天天看點

三、棧:順序棧和鍊式棧 java語言詳解棧

(總結源自《大話資料結構》和極客時間“資料結構與算法之美”專欄,初學資料結構推薦此書,進階學習推薦該專欄)

目錄

棧的順序存儲結構(JAVA語言實作)

入棧操作

出棧操作

棧的鍊式存儲結構(C語言實作)

入棧操作

出棧操作

兩棧共享空間

棧(stack)是限定僅在表尾進行插入和删除操作的線性表

我們把允許插入和删除的一端稱為棧頂(top),另一端稱為棧底(bottom),不含任何資料元素的棧稱為空棧。

棧又稱為後進先出(Last In First Out)的線性表,簡稱LIFO結構。

也就是說“棧”是一個有限制條件的線性表,那為什麼我們不直接用數組和連結清單,而用這個功能更加受限制的資料結構呢?

當某個資料集合隻涉及在⼀端插⼊和删除資料,并且滿⾜後進先出、先進後出的特性,我們就應該⾸選“棧”這種資料結構。 

三、棧:順序棧和鍊式棧 java語言詳解棧

出棧隻能是棧頂元素。

棧的順序存儲結構(JAVA語言實作)

既然棧隻能由一端進行插入和删除,那麼用哪一段呢?

下标為0的一端作為棧底比較好,因為首元素都存在棧底,變化最小,是以它作棧底。

//	基于數組實作的順序棧 
public	class	ArrayStack	{
        private	String[]	items;		//數組
        private	int	count;	        	//棧中元素個數
        private	int	n;			//棧的⼤⼩

        //	初始化數組,申請⼀個⼤⼩為n的數組空間
        public	ArrayStack(int	n)	{
                this.items = new String[n];
                this.n = n;
                this.count = 0;
		}
    
        //入棧操作
        public	boolean	push(String item)	{下面再寫}
        
        //出棧操作
        public	String	pop()	{下面在寫}

}           

入棧操作

進棧操作的思想就是:①将item放到下标為count的位置,②count++,指向下一個可用放入元素的位置。

// ⼊棧操作
public	boolean	push(String item){
    //數組空間不夠了,直接傳回false,⼊棧失敗。
    if(count==n) return	false;
    //将item放到下标為count的位置,并且count加⼀
    items[count]=item;
    ++count;
    return true;
}           

出棧操作

出棧操作的思想很簡單:count--即可。因為再出棧的話,再取出資料以後往下減即可,而入棧的時候,會覆寫掉之前的資料。

//出棧操作
public String pop()	{
//棧為空,則直接傳回null
    if (count == 0)
        return null;
//傳回下标為count-1的數組元素,并且棧中元素個數count減⼀
    String tmp = items[count-1];
    --count;
    return tmp;
}           

動态擴容的順序棧:即底層采用了一個動态擴容的數組:入棧時,若數組空間不夠,我們就重新申請⼀塊更⼤的記憶體,将原來數組中資料統統拷⻉過去。由于動态擴容這個動作不時常發生,均攤了時間成本。故時間複雜度不變,為O(1)。

棧的鍊式存儲結構(C語言實作)

我們把棧頂放在單連結清單的頭部,删去頭結點,因為棧的鍊式結構中不需要頭結點,沒有意義。

//鍊棧結構代碼如下
typedef struct StackNode
{
    SElemType data;
    struct StackNode *next;
}StackNode,*LinkStackPtr;

typedef struct LinkStack
{
    LinkStackPtr top;
    int count;
}LinkStack;           

入棧操作

入棧的思路:既然棧頂指針代替了頭結點,那麼棧頂指針肯定是要發揮作用的。

開辟一個新的結點,資料域放新的資料元素,再讓它的next指向現在的棧頂指針所指的元素,随後再讓棧頂指針指向新結點即可。

Status Push(LinkStack *s,SElemType e)
{
    LinkStackPtr s=(LinkStackPtr)malloc(sizeof(StackNode));
    s->data=e;
    s->next=S->top;
    S->top=s;
    S->count++;
    return OK;
}           

出棧操作

出棧思路:用一個變量p指向棧頂空間,棧頂指針下移,free掉p

//若棧不空,則删除S的棧頂元素,用e傳回其值,并傳回OK,否則傳回ERROR
Status Pop(LinkStack *S,SElemType *e)
{
    LinkStackPtr P;

    if(StackEmpty(*S))
        return ERROR;

    *e=S->top->data;
    p=S->top;

    S->top=S->top->next;
    free(p);
    S->count--;
    return OK;
}           

順序棧與鍊棧的進棧和出棧都是O(1)的時間複雜度,O(1)的空間複雜度。【有人可能會問:這不是用到了記憶體空間麼,怎麼會是O(1)的空間複雜度呢? 我們說空間複雜度,是指除了原本的資料存儲空間外,算法運⾏還需要額外的存儲空間。而這裡的數組也好,連結清單也好,所占空間都是必須的,是以不能算進空間複雜度裡去。】

而順序棧長度固定,鍊棧有指針域,二者都存在記憶體空間浪費問題。

若棧的使用過程中,元素變化不可預料,時大時小,最好用鍊棧,若變化在可控範圍内,建議用順序棧。

兩棧共享空間

棧很友善,因為不存線上性表的插入和删除時需要移動元素的問題。但它同樣有缺陷,因為必須事先确定數組存儲空間的大小。對于兩個相同類型的棧,我們可以最大限度的利用開辟的存儲空間,怎麼用呢?就是我們的兩棧共享!

怎麼個共享法呢?共享共享,一定是多個對象共同使用同一個對象。我們知道,數組有兩端,而棧隻有一個棧底,那麼能不能兩端各安一個棧底,往數組之間延伸呢?這就是我們的兩棧共享。

typedef struct
{
    SElemType data[MAXSIZE];
    int top1;  //棧1的棧頂指針
    int top2;  //棧2的棧頂指針
}SqDoubleStack;
           

ok,現在兩個棧共享一個數組空間,那插入的時候如何知道插入到哪個棧呢?是以我們需要有個小東西來執行一個職能:插入之前告訴我,你這個元素要插入到哪個棧,即stackNumber

//插入操作
Status Push(SqDoubleStack *s,SElemType e,int stackNumber)
{
    if(S->top1+1==S->top2)
        return ERROR;  //此時棧滿

    if(stackNumber==1)
        S->data[++S->top1]=e;   //往top1+1位置處插入元素
    else if(stackNumber==2)
        S->data[--S->top2]=e;    //往top2-1位置處插入元素
    return OK;
}           
//删除操作
//若棧不空,删除S的棧頂元素,用e傳回其值,并傳回OK,否則傳回ERROR
Status Pop(SqDoubleStack *S, SElemType *e,int stackNumber)
{
    if(stackNumber==1)    
    {
        if(S->top1 ==-1)
            retrun ERROR;
        *e=S->data[S->top1--];
    }

    if(stackNumber==2)    
    {
        if(S->top2 ==MAXSIZE)
            retrun ERROR;
        *e=S->data[S->top2++];
    }
    return OK;
}           

那麼有個問題來了:共享歸共享,它還是在數組裡面,數組還是固定大小的呀,那有什麼用呢?是以呀,這種資料結構一般用于兩個棧的空間需求有相反關系時,即一個增長時另一個在縮短,若兩個都在不停增長,那很快就會因為棧滿而溢出。