1. 程式人生 > >淺談算法和數據結構: 一 棧和隊列

淺談算法和數據結構: 一 棧和隊列

操作 拷貝 ray 對數 () stack實現 定義 pub for

原文出自:http://www.cnblogs.com/yangecnu/p/Introduction-Stack-and-Queue.html

1. 基本概念

概念很簡單,棧 (Stack)是一種後進先出(last in first off,LIFO)的數據結構,而隊列(Queue)則是一種先進先出 (fisrt in first out,FIFO)的結構,如下圖:

技術分享

2. 實現

現在來看如何實現以上的兩個數據結構。在動手之前,Framework Design Guidelines這本書告訴我們,在設計API或者實體類的時候,應當圍繞場景編寫API規格說明書。

1.1 Stack的實現

棧是一種後進先出的數據結構,對於Stack 我們希望至少要對外提供以下幾個方法:

技術分享

Stack<T>()

創建一個空的棧

void Push(T s)

往棧中添加一個新的元素

T Pop()

移除並返回最近添加的元素

boolean IsEmpty()

棧是否為空

int Size()

棧中元素的個數

要實現這些功能,我們有兩中方法,數組和鏈表,先看鏈表實現:

棧的鏈表實現:

我們首先定義一個內部類來保存每個鏈表的節點,該節點包括當前的值以及指向下一個的值,然後建立一個節點保存位於棧頂的值以及記錄棧的元素個數;

class Node
{
    public T Item{get;set;}
    
public Node Next { get; set; } } private Node first = null; private int number = 0;

現在來實現Push方法,即向棧頂壓入一個元素,首先保存原先的位於棧頂的元素,然後新建一個新的棧頂元素,然後將該元素的下一個指向原先的棧頂元素。整個Pop過程如下:

技術分享

實現代碼如下:

void Push(T node)
{
    Node oldFirst = first;
    first = new Node();
    first.Item= node;
    first.Next = oldFirst;
    number
++; }

Pop方法也很簡單,首先保存棧頂元素的值,然後將棧頂元素設置為下一個元素:

技術分享

T Pop()
{
    T item = first.Item;
    first = first.Next;
    number--;
    return item;
}

基於鏈表的Stack實現,在最壞的情況下只需要常量的時間來進行Push和Pop操作。

棧的數組實現:

我們可以使用數組來存儲棧中的元素Push的時候,直接添加一個元素S[N]到數組中,Pop的時候直接返回S[N-1].

技術分享

首先,我們定義一個數組,然後在構造函數中給定初始化大小,Push方法實現如下,就是集合裏添加一個元素:

T[] item;
int number = 0;

public StackImplementByArray(int capacity)
{
    item = new T[capacity];
}
public void Push(T _item)
{
    if (number == item.Length) Resize(2 * item.Length);
    item[number++] = _item;
}

Pop方法:

public T Pop()
{
    T temp = item[--number];
    item[number] = default(T);
    if (number > 0 && number == item.Length / 4) Resize(item.Length / 2);
    return temp;
}

在Push和Pop方法中,為了節省內存空間,我們會對數組進行整理。Push的時候,當元素的個數達到數組的Capacity的時候,我們開辟2倍於當前元素的新數組,然後將原數組中的元素拷貝到新數組中。Pop的時候,當元素的個數小於當前容量的1/4的時候,我們將原數組的大小容量減少1/2。

Resize方法基本就是數組復制:

private void Resize(int capacity)
{
    T[] temp = new T[capacity];
    for (int i = 0; i < item.Length; i++)
    {
        temp[i] = item[i];
    }
    item = temp;
}

當我們縮小數組的時候,采用的是判斷1/4的情況,這樣效率要比1/2要高,因為可以有效避免在1/2附件插入,刪除,插入,刪除,從而頻繁的擴大和縮小數組的情況。下圖展示了在插入和刪除的情況下數組中的元素以及數組大小的變化情況:

技術分享

分析:1. Pop和Push操作在最壞的情況下與元素個數成比例的N的時間,時間主要花費在擴大或者縮小數組的個數時,數組拷貝上。

2. 元素在內存中分布緊湊,密度高,便於利用內存的時間和空間局部性,便於CPU進行緩存,較LinkList內存占用小,效率高。

2.2 Queue的實現

Queue是一種先進先出的數據結構,和Stack一樣,他也有鏈表和數組兩種實現,理解了Stack的實現後,Queue的實現就比較簡單了。

技術分享

Stack<T>()

創建一個空的隊列

void Enqueue(T s)

往隊列中添加一個新的元素

T Dequeue()

移除隊列中最早添加的元素

boolean IsEmpty()

隊列是否為空

int Size()

隊列中元素的個數

首先看鏈表的實現:

Dequeue方法就是返回鏈表中的第一個元素,這個和Stack中的Pop方法相似:

public T Dequeue()
{
    T temp = first.Item;
    first = first.Next;
    number--;
    if (IsEmpety())
        last = null;
    return temp;
}

Enqueue和Stack的Push方法不同,他是在鏈表的末尾增加新的元素:

public void Enqueue(T item)
{
    Node oldLast = last;
    last = new Node();
    last.Item = item;
    if (IsEmpety())
    {
        first = last;
    }
    else
    {
        oldLast.Next = last;
    }
    number++;
}

同樣地,現在再來看如何使用數組來實現Queue,首先我們使用數組來保存數據,並定義變量head和tail來記錄Queue的首尾元素。

技術分享

和Stack的實現方式不同,在Queue中,我們定義了head和tail來記錄頭元素和尾元素。當enqueue的時候,tial加1,將元素放在尾部,當dequeue的時候,head減1,並返回。

public void Enqueue(T _item)
{
    if ((head - tail + 1) == item.Length) Resize(2 * item.Length);
    item[tail++] = _item;
}

public T Dequeue()
{
    T temp = item[--head];
    item[head] = default(T);
    if (head > 0 && (tail - head + 1) == item.Length / 4) Resize(item.Length / 2);
    return temp;
}

private void Resize(int capacity)
{
    T[] temp = new T[capacity];
    int index = 0;
    for (int i = head; i < tail; i++)
    {
        temp[++index] = item[i];
    }
    item = temp;
}

淺談算法和數據結構: 一 棧和隊列