本文內(nèi)容:
一械媒、棧
1目锭、什么是棧评汰?
2纷捞、棧的操作集.
3、棧的 C 實(shí)現(xiàn).二被去、隊(duì)列
1主儡、什么是隊(duì)列?
2惨缆、隊(duì)列的操作集.
3糜值、隊(duì)列的 C 實(shí)現(xiàn).
工程代碼 Github: Data_Structures_C_Implemention -- Stack & Queue
一坯墨、棧
1寂汇、什么是棧?
棧 (Stack)捣染,是限制插入與刪除操作只能在末端 (Top) 進(jìn)行的表骄瓣,而這個(gè)末端也稱為棧頂;
它有時(shí)候也會被稱為耍攘,先進(jìn)后出 (LIFO: Last In First Out) 表;
棧的抽象模型圖:2榕栏、棧的操作集.
從上面的模型圖中就可以看出,棧的核心操作有: Push蕾各、Pop扒磁、Top (Peek) 三個(gè)操作;
棧操作集圖示:3式曲、棧的 C 實(shí)現(xiàn).
棧的實(shí)現(xiàn)方式妨托,有兩類,解析:
1吝羞、數(shù)組的實(shí)現(xiàn)方式始鱼,我覺得不需要過多的解釋了遮婶,鏈表就是為了解決數(shù)組的缺點(diǎn)而被設(shè)計(jì)出來的盒音;
那么為什么要使用單鏈表來實(shí)現(xiàn)表神僵,而不是其它鏈表形式呢卷胯?
首先枢析,單鏈表是所有鏈表里面最簡單的鏈表坞淮,空間占用也是最小的耀里,也就是開銷最芯伦浮筒捺;只要證明單鏈表可以實(shí)現(xiàn)即可以了柏腻。
棧是一個(gè)表,而且是只能在一個(gè)端口進(jìn)行插入與刪除操作系吭,遍歷方向是從棧底到棧頂五嫂;
而單鏈表也是一個(gè)表,而且它的操作可以在任意位置進(jìn)行插入與刪除,遍歷方向是鏈頭與鏈尾沃缘;
從上面的兩個(gè)結(jié)論來看躯枢,棧可以看作是單鏈表的其中一種情況槐臀;
2锄蹂、在這里 tail
指針的指向改了一下,把它放在鏈頭 [一般習(xí)慣性左邊是指鏈頭] 而它指向的就是最后一個(gè)壓入的節(jié)點(diǎn)水慨,也就是說左邊的第一個(gè)節(jié)點(diǎn)就是真正的鏈尾得糜;這樣設(shè)計(jì)的目的就是為了讓鏈表可以從鏈尾直接進(jìn)行遍歷操作,而且所有的插入與刪除操作在鏈尾就可以實(shí)現(xiàn)晰洒;【您如果覺得暈朝抖,就先保有疑惑,等到查看下面的棧的入棧與出棧操作的具體代碼實(shí)現(xiàn)的時(shí)候谍珊,我相信您就懂了槽棍!】
- 節(jié)點(diǎn)[內(nèi)存結(jié)構(gòu)]與棧的定義:
節(jié)點(diǎn)[內(nèi)存結(jié)構(gòu)],
struct __StackNode {
ElementTypePrt data;
#if _C_STACK_LINKEDLIST_IMP
StackNode next;
#else
int prevIdx;
#endif
};
解析:
1抬驴、_C_STACK_LINKEDLIST_IMP
原型是 #define _C_STACK_LINKEDLIST_IMP 1
就是一個(gè)宏開關(guān)炼七,1
的時(shí)候是使用單鏈表的方式實(shí)現(xiàn)棧,0
就是使用數(shù)組的方式實(shí)現(xiàn)棧布持;
2豌拙、ElementTypePrt
原型是 typedef void * ElementTypePrt;
,使用 typedef
是方便后期進(jìn)行修改题暖;這里使用指針的目的是為了讓鏈表支持更多的數(shù)據(jù)類型按傅,使代碼的可擴(kuò)展性更強(qiáng);【如: data 可以直接指向一個(gè)單純的 int
或者 一個(gè) struct
胧卤,又或者是一個(gè) list
等等】
3唯绍、在數(shù)組實(shí)現(xiàn)的方式下,prevIdx
這個(gè)參量其實(shí)可以不要的枝誊,看君愛好吧况芒,反正入棧、出棧與它無關(guān)叶撒;
棧绝骚,
typedef struct __StackNode * _StackNode;
typedef _StackNode StackNode;
typedef struct __Stack * _Stack;
typedef _Stack Stack;
struct __Stack {
unsigned int size;
MatchFunc matchFunc;
DestroyFunc destroyFunc;
#if _C_STACK_LINKEDLIST_IMP
StackNode tail;
#else
unsigned int capacity;
int tailIdx;
StackNode nodes;
#endif
};
解析:
1、鏈表實(shí)現(xiàn)方式下祠够,
#if _C_STACK_LINKEDLIST_IMP
StackNode tail;
#else
是取消了 head
指針压汪,只保留了單鏈表的 tail
指針;
2古瓤、數(shù)組實(shí)現(xiàn)方式下止剖,
#else
unsigned int capacity;
unsigned int tailIdx;
StackNode nodes;
#endif
引入一個(gè) capacity
參量腺阳,我們知道,C 語言中數(shù)組初始化是要指定長度的穿香,而這個(gè)參量就是表明數(shù)組的最大長度亭引,也就是可以存儲多少個(gè)節(jié)點(diǎn);
tailIdx
就是棧頂節(jié)點(diǎn)的下標(biāo)扔水;
nodes
是一個(gè)結(jié)構(gòu)體指針痛侍,相當(dāng)于一個(gè)數(shù)組的首指針朝氓,數(shù)組中保存的是 struct __StackNode
魔市;
- 棧的核心操作集:
/* Stack Create */
#if _C_STACK_LINKEDLIST_IMP
Stack Stack_Create(DestroyFunc des);
#else
Stack Stack_Create(unsigned int cap, DestroyFunc des);
#endif
void Stack_Init(Stack s, DestroyFunc des);
void Stack_Destroy(Stack s);
/* Stack Operations */
_BOOL Stack_Push(Stack s, ElementTypePrt x);
_BOOL Stack_Pop(Stack s, ElementTypePrtPrt data);
ElementTypePrt Stack_Peek(Stack s);
_BOOL Stack_PeekAndPop(Stack s, ElementTypePrtPrt data);
- 棧的創(chuàng)建與銷毀:
創(chuàng)建,
【鏈?zhǔn)綄?shí)現(xiàn)】Stack Stack_Create(DestroyFunc des);
Stack Stack_Create(DestroyFunc des) {
Stack s = (Stack)(malloc(sizeof(struct __Stack)));
if (s == NULL) { printf("ERROR: Out Of Space !"); return NULL; }
Stack_Init(s, des);
return s;
}
解析:
1赵哲、DestroyFunc
原型是 typedef void(*DestroyFunc) (void * data);
指向形如 void(*) (void * data);
的函數(shù)指針待德;data
如何進(jìn)行釋放也由使用者決定;也是為了代碼可擴(kuò)展性枫夺;
2将宪、malloc
原型是 void * malloc(size_t size)
;
3橡庞、Stack_Init(s, des);
较坛,請移步面下面的解釋 初始化;
【數(shù)組實(shí)現(xiàn)】Stack Stack_Create(unsigned int cap, DestroyFunc des);
Stack Stack_Create(unsigned int cap, DestroyFunc des) {
if (cap == 0) { printf("ERROR: Bad Cap Parameter [cap > 0] !"); return NULL; }
if (cap > STACK_MAXELEMENTS) { printf("ERROR: Bad Cap Parameter [cap < STACK_MAXELEMENTS(%d)] !", STACK_MAXELEMENTS); return NULL; }
Stack s = (Stack)(malloc(sizeof(struct __Stack)));
if (s == NULL) { printf("ERROR: Out Of Space !"); return NULL; }
s->nodes = malloc(sizeof(StackNode) * cap);
if (s->nodes == NULL) { printf("ERROR: Out Of Space !"); free(s); return NULL; }
s->capacity = cap;
Stack_Init(s, des);
return s;
}
解析:
1扒最、形參 unsigned int cap
就是數(shù)組初始化的最大內(nèi)存空間丑勤;
2、STACK_MAXELEMENTS
原型是 #define STACK_MAXELEMENTS 100
;
3吧趣、s->nodes = malloc(sizeof(StackNode) * cap);
這里就是與鏈?zhǔn)綄?shí)現(xiàn)最大的區(qū)別法竞,提前申請要保存節(jié)點(diǎn)的內(nèi)存空間;
4强挫、Stack_Init(s, des);
岔霸,請移步面下面的解釋 初始化;
初始化俯渤,
void Stack_Init(Stack s, DestroyFunc des) {
if (s == NULL) { printf("ERROR: Please Using Stack_Create(...) First !"); return; }
s->size = LINKEDLIST_EMPTY;
s->matchFunc = NULL;
s->destroyFunc = des;
#if _C_STACK_LINKEDLIST_IMP
s->tail = NULL;
#else
s->tailIdx = STACK_INVAILDINDEX;
#endif
}
解析呆细,
里面的參量直接進(jìn)行賦值為空就可以了,其中 LINKEDLIST_EMPTY
原型是 #define LINKEDLIST_EMPTY 0
八匠,STACK_INVAILDINDEX
原型是 #define STACK_INVAILDINDEX -1
;
銷毀侦鹏,
void Stack_Destroy(Stack s) {
if (s == NULL) { printf("ERROR: Please Using Stack_Create(...) First !"); return; }
ElementTypePrt data;
while (!Stack_IsEmpty(s)) {
if ((Stack_Pop(s, (ElementTypePrtPrt)&data) == LINKEDLIST_TRUE) &&
(s->destroyFunc != NULL)) {
s->destroyFunc(data);
}
}
memset(s, 0, sizeof(struct __Stack));
}
解析,運(yùn)作原理是不停地做出棧處理臀叙,直到椔运空為止,就是清空棧的作用劝萤;
1渊涝、Stack_IsEmpty(s)
原型是 _BOOL Stack_IsEmpty(Stack s) { return (s->size == LINKEDLIST_EMPTY); }
2、Stack_Pop(s, (ElementTypePrtPrt)&data) == LINKEDLIST_TRUE)
請移步下面的 出棧操作
- 入棧操作:
_BOOL Stack_Push(Stack s, ElementTypePrt x) {
if (s == NULL) { printf("ERROR: Please Using Stack_Create(...) First !"); return LINKEDLIST_FALSE; }
StackNode nNode;
nNode = malloc(sizeof(struct __StackNode));
if (nNode == NULL) { printf("ERROR: Out Of Space ! "); return LINKEDLIST_FALSE; }
nNode->data = x;
#if _C_STACK_LINKEDLIST_IMP
nNode->next = NULL;
if (Stack_IsEmpty(s)) {
s->tail = nNode;
} else {
/* Get Tail */
StackNode tail = s->tail;
/* Push Operations */
nNode->next = tail;
/* Set Tail */
s->tail = nNode;
}
/* Size ++ */
s->size++;
#else
/* Size ++ */
s->size++;
if (s->size > s->capacity) {
printf("ERROR: Out Of Space ! ");
s->size--;
return LINKEDLIST_FALSE;
}
nNode->prevIdx = s->tailIdx;
s->tailIdx++;
s->nodes[s->tailIdx] = *nNode;
#endif
return LINKEDLIST_TRUE;
}
解析,
入棧操作圖示跨释,
// 對應(yīng)的核心代碼
【鏈?zhǔn)綄?shí)現(xiàn)】
nNode->next = tail;
s->tail = nNode;
【數(shù)組實(shí)現(xiàn)】// 對應(yīng)的核心代碼
【數(shù)組實(shí)現(xiàn)】
s->tailIdx++;
s->nodes[s->tailIdx] = *nNode;
- 出棧操作:
_BOOL Stack_Pop(Stack s, ElementTypePrtPrt data) {
if (s == NULL) { printf("ERROR: Bad Stack !"); return LINKEDLIST_FALSE; }
if (Stack_IsEmpty(s)) { printf("ERROR: Empty Stack !"); return LINKEDLIST_FALSE; }
#if _C_STACK_LINKEDLIST_IMP
StackNode lDelete = s->tail;
/* Get Data */
*data = lDelete->data;
/* Pop Operations */
s->tail = s->tail->next;
/* Free The Deleted Node */
free(lDelete);
#else
*data = s->nodes[s->tailIdx].data;
s->tailIdx--;
#endif
/* Size -- */
s->size--;
return LINKEDLIST_TRUE;
}
解析胸私,
出棧操作圖示,
【鏈?zhǔn)綄?shí)現(xiàn)】// 對應(yīng)的核心代碼
StackNode tail = s->tail;
s->tail = s->tail->next;
free(lDelete);
【數(shù)組實(shí)現(xiàn)】// 對應(yīng)的核心代碼
s->tailIdx--;
二鳖谈、隊(duì)列
1岁疼、什么是隊(duì)列?
隊(duì)列 (Queue)缆娃,是限制插入操作在一端捷绒,而刪除操作要在另一端進(jìn)行的表;
它有時(shí)候也會被稱為贯要,先進(jìn)先出 (FIFO: First In First Out) 表;
隊(duì)列的抽象模型圖:2暖侨、隊(duì)列的操作集.
從上面的模型圖中就可以看出,隊(duì)列的核心操作集有: Enqueue崇渗、Dequeue 兩個(gè)操作字逗;
隊(duì)列操作集圖示:3、隊(duì)列的 C 實(shí)現(xiàn).
隊(duì)列其實(shí)更接近單鏈表了宅广,具備頭和尾葫掉,當(dāng)然遍歷方向也是從頭到尾,所以直接使用單鏈表來實(shí)現(xiàn)就可以了跟狱,不需要做太多的修改俭厚;
不過這里的,數(shù)組實(shí)現(xiàn)就要有點(diǎn)技巧了兽肤;因?yàn)殛?duì)列是一個(gè)端口進(jìn)套腹,另一個(gè)端口出,也就是要有一個(gè)指向進(jìn)入方向的下標(biāo) headIdx
资铡,以及出方向的下標(biāo) tailIdx
电禀;
這里要注意的是, headIdx
與 tailIdx
的大小關(guān)系是不定的笤休,這是由于數(shù)組自初始化后尖飞,空間是固定的,而在頻繁的入隊(duì)與出隊(duì)操作后店雅,會出現(xiàn) headIdx > tailIdx
政基、headIdx < tailIdx
、headIdx = tailIdx
'這三種情況闹啦,而且它們會不停地進(jìn)行切換沮明;【當(dāng)然這里也是要在代碼實(shí)現(xiàn)的時(shí)候要特別細(xì)心處理的地方】
- 節(jié)點(diǎn)[內(nèi)存結(jié)構(gòu)]與隊(duì)列的定義:
節(jié)點(diǎn),【與棧節(jié)點(diǎn)定義一致】
typedef struct __QueueNode * _QueueNode;
typedef _QueueNode QueueNode;
struct __QueueNode {
ElementTypePrt data;
#if _C_QUEUE_LINKEDLIST_IMP
QueueNode next;
#else
int prevIdx;
#endif
};
隊(duì)列窍奋,
typedef struct __Queue * _Queue;
typedef _Queue Queue;
struct __Queue {
unsigned int size;
MatchFunc matchFunc;
DestroyFunc destroyFunc;
#if _C_QUEUE_LINKEDLIST_IMP
QueueNode head;
QueueNode tail;
#else
unsigned int capacity;
int headIdx;
int tailIdx;
QueueNode nodes;
#endif
};
解析荐健,
與棧對比酱畅,鏈?zhǔn)綄?shí)現(xiàn)下,重新引入 QueueNode head;
指針江场,用于進(jìn)行出隊(duì)的操作纺酸;數(shù)組實(shí)現(xiàn)下,引入 int headIdx;
方便進(jìn)行出隊(duì)操作址否;
- 隊(duì)列核心操作集:
/* Queue Create */
#if _C_QUEUE_LINKEDLIST_IMP
Queue Queue_Create(DestroyFunc des);
#else
Queue Queue_Create(unsigned int cap, DestroyFunc des);
#endif
void Queue_Init(Queue q, DestroyFunc des);
void Queue_Destroy(Queue q);
/* Queue Operations */
_BOOL Queue_Enqueue(Queue q, ElementTypePrt x);
_BOOL Queue_Dequeue(Queue q, ElementTypePrtPrt data);
ElementTypePrt Queue_Peek(Queue q);
_BOOL Queue_PeekAndDequeue(Queue q, ElementTypePrtPrt data);
解析餐蔬,_C_QUEUE_LINKEDLIST_IMP
原型是 #define _C_QUEUE_LINKEDLIST_IMP 1
鏈?zhǔn)綄?shí)現(xiàn)或數(shù)組實(shí)現(xiàn)的開關(guān);
- 隊(duì)列的創(chuàng)建與銷毀:
創(chuàng)建佑附,這里的代碼實(shí)現(xiàn)與棧的實(shí)現(xiàn)一致樊诺;
初始化,
void Queue_Init(Queue q, DestroyFunc des) {
if (q == NULL) { printf("ERROR: Please Using Queue_Create(...) First !"); return; }
q->size = LINKEDLIST_EMPTY;
q->matchFunc = NULL;
q->destroyFunc = des;
#if _C_QUEUE_LINKEDLIST_IMP
q->tail = NULL;
#else
q->headIdx = 0;
q->tailIdx = QUEUE_INVAILDINDEX;
#endif
}
解析帮匾,這里要注意的是啄骇,在 數(shù)組實(shí)現(xiàn)方式下痴鳄,
#else
q->headIdx = 0;
q->tailIdx = QUEUE_INVAILDINDEX; // -1
#endif
headIdx = 0
這里選擇 0
而不是 QUEUE_INVAILDINDEX
瘟斜,因?yàn)檫@樣做在入隊(duì)操作的時(shí)候就可以使用headIdx
而不用增加一個(gè)判斷【您可以先留有疑惑,結(jié)合下面的入隊(duì)操作痪寻,我相信您就懂了】螺句;
銷毀,
與棧的出棧操作原理上是一樣橡类,只不過這里使用的是隊(duì)列的出隊(duì)操作罷了蛇尚;
void Queue_Destroy(Queue q) {
if (q == NULL) { printf("ERROR: Please Using Queue_Create(...) First !"); return; }
ElementTypePrt data;
while (!Queue_IsEmpty(q)) {
if ((Queue_Dequeue(q, (ElementTypePrtPrt)&data) == LINKEDLIST_TRUE) &&
(q->destroyFunc != NULL)) {
q->destroyFunc(data);
}
}
memset(q, 0, sizeof(struct __Queue));
}
- 入隊(duì)操作:
_BOOL Queue_Enqueue(Queue q, ElementTypePrt x) {
if (q == NULL) { printf("ERROR: Please Using Queue_Create(...) First !"); return LINKEDLIST_FALSE; }
QueueNode nNode;
nNode = malloc(sizeof(struct __QueueNode));
if (nNode == NULL) { printf("ERROR: Out Of Space ! "); return LINKEDLIST_FALSE; }
nNode->data = x;
#if _C_QUEUE_LINKEDLIST_IMP
nNode->next = NULL;
if (Queue_IsEmpty(q)) {
q->head = q->tail = nNode;
} else {
/* Get Tail */
QueueNode tail = q->tail;
/* Push Operations */
nNode->next = tail->next;
tail->next = nNode;
/* Set Tail */
q->tail = nNode;
}
/* Size ++ */
q->size++;
#else
/* Size ++ */
q->size++;
if (q->size > q->capacity) {
printf("ERROR: Out Of Space ! ");
q->size--;
return LINKEDLIST_FALSE;
}
q->tailIdx = Queue_VaildIdx(q->tailIdx, q);
nNode->prevIdx = q->tailIdx;
q->nodes[q->tailIdx] = *nNode;
#endif
return LINKEDLIST_TRUE;
}
解析,入隊(duì)操作顾画,在鏈?zhǔn)綄?shí)現(xiàn)下就直接在鏈尾進(jìn)行取劫,而數(shù)組實(shí)現(xiàn)下直接在數(shù)組的最后一個(gè)下標(biāo)節(jié)點(diǎn)進(jìn)行;
入隊(duì)操作圖示研侣,
【鏈?zhǔn)綄?shí)現(xiàn)】// 對應(yīng)的核心代碼
nNode->next = tail->next;
tail->next = nNode;
q->tail = nNode;
【數(shù)組實(shí)現(xiàn)】// 對應(yīng)的核心代碼
q->tailIdx = Queue_VaildIdx(q->tailIdx, q);
nNode->prevIdx = q->tailIdx;
q->nodes[q->tailIdx] = *nNode;
解析谱邪,這里要注意的是,tailIdx
的取值范圍是 [0 ~ (size - 1) ~ 0] 它是一個(gè)循環(huán)庶诡,而不是簡單地 taildIdx + 1
惦银;
Queue_VaildIdx
原型是
int Queue_VaildIdx(int idx, Queue q) {
if (++idx == q->capacity) { return 0; }
return idx;
}
提示,++idx == q->capacity
它的運(yùn)算過程是 idx = idx + 1, idx == q->capacity
末誓;
- 出隊(duì)操作:
_BOOL Queue_Dequeue(Queue q, ElementTypePrtPrt data) {
if (q == NULL) { printf("ERROR: Bad Queue !"); return LINKEDLIST_FALSE; }
if (Queue_IsEmpty(q)) { printf("ERROR: Empty Queue !"); return LINKEDLIST_FALSE; }
#if _C_QUEUE_LINKEDLIST_IMP
QueueNode lDelete = q->head;
/* Get Data */
*data = lDelete->data;
/* Pop Operations */
q->head = q->head->next;
/* Free The Deleted Node */
free(lDelete);
#else
*data = q->nodes[q->headIdx].data;
q->headIdx = Queue_VaildIdx(q->headIdx, q);
#endif
/* Size -- */
q->size--;
return LINKEDLIST_TRUE;
}
解析扯俱,出隊(duì)操作,在鏈?zhǔn)綄?shí)現(xiàn)下就直接在鏈頭進(jìn)行喇澡,而數(shù)組實(shí)現(xiàn)下直接在數(shù)組的第一個(gè)下標(biāo)節(jié)點(diǎn)進(jìn)行迅栅;
出隊(duì)操作圖示,
【鏈?zhǔn)綄?shí)現(xiàn)】// 對應(yīng)的核心代碼
QueueNode lDelete = q->head;
q->head = q->head->next;
free(lDelete);
【數(shù)組實(shí)現(xiàn)】// 對應(yīng)的核心代碼
q->headIdx = Queue_VaildIdx(q->headIdx, q);
參考書籍:
1晴玖、《算法精解_C語言描述(中文版)》
2读存、《數(shù)據(jù)結(jié)構(gòu)與算法分析—C語言描述》
寫到這里箩艺,本文結(jié)束!下一篇宪萄,《數(shù)據(jù)結(jié)構(gòu):集合》