1. 程式人生 > >棧+佇列的基本操作實現(嚴蔚敏版設計思路解讀c++)

棧+佇列的基本操作實現(嚴蔚敏版設計思路解讀c++)


大家好,我是集美貌與才華於一身的阿俊吶,咳咳咳…不接受任何反駁,感謝你辣麼好看還來關注我,嘿嘿嘿,讓我們進入正題叭…biu~ biu~…

這篇部落格主要是我學完資料結構(嚴蔚敏版),想記錄下來以後複習用。

  • 從零搭建起棧和佇列的操作
  • 順序動態棧和動態陣列的關係(棧也可用鏈式結構實現,不過不常用)
  • 鏈式佇列和連結串列的關係(佇列也可用順式結構實現,不過不常用)
  • 陣列與連結串列的優缺點

資料結構設計思路剖析

先聊聊基礎的順序(動態陣列)、鏈式結構(連結串列)的設計思路,再嘮嘮如何在已有基礎上衍生出比較高階的順序(動態順序棧)、鏈式結構(鏈隊)的設計

動態陣列的資料結構

//動態陣列的資料結構 
typedef struct
{
	int* elem;//陣列首地址
	int length;//陣列長度
	int listsize;//陣列可用空間
}SqList;
  • 動態陣列克服了靜態陣列無法改變空間大小的缺陷
  • 該資料結構也類似於面向物件思想,將動態陣列看做一個物件,其中包含了三個屬性,待會兒要實現的功能就是其操作

在動態陣列的基礎上新增限制,稍作變形,就是順序動態棧了

順序動態棧的資料結構

typedef struct
{
	int* base;//棧底
	int* top;//棧頂
	int listsize;//可用空間大小
}SqStack;

看看動態棧是如何由動態陣列衍生而來

  • base相當於動態陣列的首地址elem
  • top指向最後一個有效元素的下一個位置(特性)
  • top-base = 動態陣列的length
  • top = base 時棧空

連結串列的資料結構

typedef struct	LNode//連結串列 
{
	int data;//節點中的資料
	struct LNode* next;//指向下一個節點的指標
}LNode,*LinkList;

在連結串列的資料結構的構成中,可分為兩大類:資料項,指標項

  • 資料項可根據需要任意定義,靈活多變
  • 指標項雖然永遠只佔4位,但也是可指向任意節點

在連結串列的基礎上新增首尾指標,稍作限制,就是佇列啦

鏈式佇列的資料結構

typedef struct	LNode//連結串列 
{
	int data;
	struct LNode* next;
}LNode,*LinkList;
typedef struct
{
	LNode* front;//隊頭指標
	LNode* rear; //隊尾指標
}LinkQueue;

看看佇列是如何由連結串列衍生而來

  • 只需新增指向節點的頭尾指標即可,因為連結串列的特點是隻需要知道頭指標,就可遍歷整個連結串列;為什麼新增尾指標呢?因為佇列要求一端進,一端出,所以得充分利用頭和尾

功能構造思路

tips

資料結構的定義直接決定了其基本操作及演算法實現的難度,所以呀,我們學習資料結構一是為了掌握基本的結構定義,二是體會各種結構是如何構造而來,今後遇到實際問題時方有可能設計出不錯的資料結構

操作分為四個部分–增查改刪

線性表無論是動態陣列/棧還是連結串列/佇列,基本操作均逃不出四座大山–增查改刪,讓我們來給他們排排序

  • 增加元素永遠是老大,因為最開始是0,什麼都沒有,我們需建立一個表,其餘的操作方可進行,建立一個表也就是從空表開始反覆增加(插入)元素。若是一個表都不存在,我們拿什麼查改刪
  • 查詢元素是老二,為啥把它排第二,我改刪不服。若是把查詢排老四,一進行時突然發現,我該改誰呢?同時也發現了這個問題,沒有改刪的物件可咋辦,誒呀呀,你還是快回第二吧。
  • 改刪元素位置順序不太重要,看需要哪個操作多,相較之下,修改元素是比較簡單的,找到元素後修改就行,而刪除在陣列中麻煩,在連結串列方便

以上的功能排序也是具體實現時的順序,只要你掌握了動態陣列和連結串列的增查改刪

棧功能(僅有增刪,無查改)

  • 入棧–增
  • 出棧–刪

佇列功能(僅有增刪,無查改)

  • 入隊–增
  • 出隊–刪

二者由於自身特性限制,比動態陣列和連結串列的功能還少,所以實現起來也較簡單,只有兩個功能。

一個有意思的偷懶

  • 值得一提的是為啥我們選擇在連結串列的頭結點後刪除,尾節點插入
    按理說無論是連結串列的頭和尾,均可進行插入刪除操作呀。那讓我們來嘮嗑嘮嗑,假設在連結串列頭結點插入,新節點指向頭結點後一個元素,頭結點指向新節點,插入成功;再看看在尾節點刪除,刪除得知道尾節點前一個節點,誒嘿,突然發現,單向連結串列只能往後找,沒法回頭呀,這時候如果想解決問題,僅有新增一個指向尾節點前一個的指標咯。若是選擇在尾部插入,尾節點直接指向新節點就大功告成,頭結點刪除也很容易。相較之下,後者少用一個指標,更為簡單,所以我們採用簡單的方法

順序結構和鏈式結構的優缺點

  • 順序結構便於查詢,修改,只要定址即可;而刪除,增加需要移動位置,不便於實現
  • 鏈式結構便於刪除,增加,只需改變指標指向,且當節點資訊很多時,鏈式效率極高;而對於查詢,修改,必須從頭到尾遍歷
  • 二者是互補關係,各有所長,應根據實際應用選擇合適的結構

完整實現程式碼

棧實現完整程式碼

#include<iostream>
using namespace std;
#include<stdlib.h>
#define INIT_SIZE 100
#define INCREMENT 10

//top指向有元素的下一個位置
//top-base = 動態陣列的length
//base相當於動態陣列的首地址elem 
typedef struct
{
	int* top;
	int* base;
	int listsize;
}SqStack;
void InitStack(SqStack &S)
{
	S.base = (int*)malloc(INIT_SIZE*sizeof(int));
	S.top = S.base;
	S.listsize = INIT_SIZE;
}
//入棧 
void Push(SqStack &S,int e)
{
	if(S.top - S.base >= S.listsize)//動態擴容 
	{
		S.base = (int*)realloc(S.base,(S.listsize+INCREMENT)*sizeof(int));
		S.top = S.base + S.listsize;//更新棧頂 
		S.listsize = S.listsize+INCREMENT;//更新可用空間 
	}
	*S.top = e; 
	S.top++;
}
//空->true;非空->false 
bool IsEmpty(SqStack S)
{
	if(S.top == S.base)return true;
	else return false;
}
//僅刪除棧頂,不返回值 
void Pop(SqStack &S)
{
	if(!IsEmpty(S))
	{
		S.top--;//只刪除棧頂,不彈出 
	}
	else cout<<"棧空!"<<endl; 
}
//僅返回棧頂值,不刪除 
int GetTop(SqStack S)
{
	if(!IsEmpty(S))
	{
		return *(S.top-1);
	}
	else return -11111;//表示空棧 
}

int main()
{
	SqStack S;
	InitStack(S);
	for(int i =0; i <10; i++)
	{
		Push(S,i);
		cout<<GetTop(S);
	}
	cout<<endl;
	for(int i =0; i <10; i++)
	{
		cout<<GetTop(S);
		Pop(S);
	}cout<<GetTop(S);Pop(S);
	return 0;
 } 

佇列實現完整程式碼

#include<iostream>
using namespace std;
#include<stdlib.h>

//佇列基本操作
typedef struct QNode
{
	int data;
	struct QNode* next;
}QNode,LNode,*LinkList;

typedef struct
{
	QNode* front;
	QNode* rear;
}LinkQueue;

void InitQueue(LinkQueue &Q)
{
	Q.front = Q.rear = (QNode*)malloc(sizeof(QNode));//頭尾均指向頭指標 
	Q.front->next = NULL;//尾部賦空 
}
//尾部插入 
void Enqueue(LinkQueue &Q,int data)
{
	QNode* p = (QNode*)malloc(sizeof(QNode));
	p->data = data;
	p->next = NULL;
	
	Q.rear->next = p;
	Q.rear = p;
}
//空->true;非空->false 
bool IsEmpty(LinkQueue Q)
{
	if(Q.front->next)	return false;
	else return true;
}
//頭部刪除;注意刪除的節點為尾節點時,重新將尾節點指向頭結點 
void Dequeue(LinkQueue &Q)
{
	if(!IsEmpty(Q))
	{
		Q.front->next = Q.front->next->next;
		if(IsEmpty(Q))	Q.rear = Q.front;//刪除尾節點,重新將尾指標指向頭 
	}
}
//獲取隊頭元素 
int GetTop(LinkQueue &Q)
{
	if(!IsEmpty(Q))
	{
		return Q.front->next->data;
	}
	else return -111111;
}
int main()
{//模仿棧測試,你寫一個佇列測試吧
	return 0;
 }