终于有可以有时间写点数据结构的学习总结了,前段时间一直在紧张的忙一些项目,都没有空出时间来学习数据结构,现在终于可以稍微喘口气了,还是数据结构有意思,这两天看了点栈的东西,写下来总结一下,有错误的地方希望看到的朋友指出来,感激不尽。
根据学习,栈就是一种线性数据结构,栈的运算只能在表的一段进行,所以这种数据结构具有“后进先出”的特点。
接下来是栈的c语言实现。其中栈由一个top节点和bottom节点组成,这两个节点分别指向栈的顶部和底部。其中栈的组成结点是由结构体实现,结构体由数据库和指向下一个结点的指针域构成,下面是结点的c语言实现:
1 /*
2 定义实现栈的结点
3 */
4 typedef struct Node{
5 int data;
6 struct Node * pNext;
7 }NODE,* PNODE;
有了实现栈的结点,那么接下来就是该如何实现栈(具体的解释看代码):
1 /*
2 定义栈的结构,此处采用链表实现静态栈,所以仅仅需要给出栈的头部指针和底部指针即可
3 */
4 typedef struct Strack{
5 PNODE pTop;
6 PNODE pBottom;
7 }STRACK,* PSTRACK;
栈的基本操作:栈的初始化,压栈、栈的遍历、出栈等操作
1 /*
2 对栈初始化的函数
3 对栈初始化仅仅是令栈中的top和bottom指针指向一个确定的地址,并且此地址指向的是栈的头结点(头结点的引入可以
4 大大方便对栈的操作)。
5 */
6 void init_strack(PSTRACK pS){
7 pS->pTop=(PNODE)malloc(sizeof(NODE)); //定义一个新结点,这个结点就是栈的头结点,并且让pTop指向这个结点
8 if(pS->pTop==NULL){
9 printf("内存分配失败");
10 exit(-1);
11 }
12 else{
13 pS->pBottom=pS->pTop; //令bottom和top都指向头结点,则初始化栈完成,栈中没有任何有效结点
14 pS->pTop->pNext=NULL;
15 }
16
17 }
18
19 /*
20 压栈函数
21 因为栈具有“先进后出、后进先出”的特性,所以,在将元素压入栈时,也只能将栈压入栈的顶部
22 由此,压栈函数就是令栈的top指针指向新结点,新结点的指针域指向未压入栈时的最顶部的元素。
23 */
24 int push(PSTRACK pS,int val){
25 PNODE pNew=(PNODE)malloc(sizeof(NODE)); //定义一个新结点
26 if(pNew->pNext==NULL){
27 printf("内存分配失败");
28 exit(-1);
29 }
30 pNew->data= val;
31 pNew->pNext=pS->pTop; //指针赋值的顺序不可以乱,为保证top所指向的地址不丢失,所以首先让新结点的指针域指向pS->pTop所指向的结点
32 pS->pTop=pNew; //令top指针指向新的结点
33 return 0; //0表示当前push成功
34 }
35
36 /*
37 栈的遍历函数
38 因为只是遍历输出,所以不能破坏栈原有的结构,所以我们只有定义一个新的结点p,让其指向栈顶元素,
39 然后遍历下移一个输出其指向的元素的值 ,循环条件就是p=ps->pBottom
40 */
41 void traverse(PSTRACK ps){
42 PNODE p=ps->pTop; //让新定义的结构体指针指向栈顶元素
43 while(p!=ps->pBottom){
44 printf("%d ", p->data);
45 p=p->pNext; //让当前指针指向当前节点的下一个结点
46 }
47 printf("\n");
48 return;
49 }
50
51
52 /*
53 判断当前栈是否为空的函数
54 若为空,返回true
55 否则,返回false
56 */
57 bool isEmpty(PSTRACK ps){
58 if(ps->pTop==ps->pBottom){
59 return true;
60 }
61 else{
62 return false;
63 }
64 }
65
66 /*
67 弹栈函数:弹栈简单的说就是将第一个数值弹出,然后将ps->pTop指向原第一个结点的下一个结点(即弹栈后的第一个结点),
68 然后在将被弹出的元素在内存中释放掉。
69 */
70 bool pop(PSTRACK ps,int *pVal){
71 if(isEmpty(ps)){ //判断当前栈是否为空,若为空,则返回false
72 return false;
73 }
74 else{
75 PNODE p=ps->pTop; //定义一个结点,这个结点在每次弹栈时都是指向栈顶,这样可以保证不会造成被弹出元素的丢失
76 ps->pTop=p->pNext; //让top结点指向栈顶元素
77 *pVal=p->data;
78 free(p); //将被弹出元素释放
79 p=NULL;
80 return true;
81 }
82 }
上述就实现了对栈的基本操作,当然具体的应用还要看具体的算法要求。
上述是在大学时学习的,下面的是工作两年后再次学习时的理解,可能会有不同的见解,都写出来吧。
1、栈的定义:一种可以实现“先进后出”的数据存储结构,数据的插入和删除只能在数据的一端进行。
内存分为静态内存和动态内存。
静态内存在栈中分配;动态内存在堆中分配。
栈:由操作系统自动分配释放,存放函数的参数值,局部变量的值等。
堆:一般由程序猿分配释放,若程序猿不释放,程序结束时由OS回收(类似于c语言中的malloc函数分配的空间)。
2、栈的分类:
(1)静态栈:以数组作为数据的存储。
(2)动态栈:以链表作为数据的存储方式。
3、栈的相关操作(该处采用链表的动态栈):
一点说明:
因为用链表实现栈,其实其本质就是一个链表,只不过对该链表的插入(push)和删除(pop)操作都在该链表的一端进行(该段被称为栈顶,且人为限制就只在该栈顶进行操作),所以该链表就会具有了“先进后出”的特性,则被称为栈结构。
所以,严格来说,栈仅仅需要一个栈顶,该栈顶指向该链表的被称为栈顶端的节点(所以,该栈顶也是一个该Node类型的指针)。
(1)栈中每个节点的结构:
(2)栈的结构:
因为通过栈顶可以找到该栈的所有元素,所以,该栈对应的链表应该是一个自上而下指向的链表。
该栈仅需要一个stop指针,指向栈顶元素。
(3)栈的初始化操作:
(4)栈的push操作:
(5)栈的pop操作:
(6)栈是否为空及栈的遍历操作:
其实在栈中可以增加一个PNODE类型的指针sbuttom,让该指针用于指向栈低节点,在判断非空时,只要判断sc->stop == sc->sbuttom时,就表示top和buttom重合了,指向了最底部的节点。其实,该buttom节点可以通过最后一个节点的next指针域是否为NULL来判断(next指针域为NULL时,就表示该节点没有下一个节点了,是栈低节点)。
1 #include <stdio.h>
2 #include <sys/malloc.h>
3 #include <stdlib.h>
4
5 /*
6 栈结构及其操作:
7 当前采用链表进行栈中数据的存储。
8 */
9
10
11 /*
12 定义栈中每一个节点的结构
13 */
14 typedef struct Node{
15 int data; //数据域
16 struct Node * pnext; //指针域,指向栈中下一个节点
17 }NODE, * PNODE; //定义两个别名,方便操作
18
19 /*
20 定义栈的结构:
21 因为用链表实现栈,其实其本质也是一个链表,只不过该链表的插入(push)和删除(pop)操作只能在链表的一端(该端被称作栈顶)进行操作,所以该链表具有“先进后出”的特性,被称作栈(个人理解)。
22 所以,严格来说,栈仅仅需要一个指向栈顶(链表的一端)struct Node *类型的指针就可以知道该栈中的所有数据。
23 */
24 typedef struct Stack{
25 struct Node * stop; //栈的栈顶(指向栈中最顶部的那个元素)
26 } STACK;
27
28 //栈的初始化操作
29 struct Stack init_stack();
30
31 //push操作
32 void stack_push(STACK *sc, int data);
33
34 //pop操作
35 int pop(STACK *sc);
36
37 //遍历操作
38 void trvel_stack(STACK *sc);
39
40 //判断是否为空操作
41 int is_empty(STACK *sc);
42
43 int main(int argc, char const *argv[])
44 {
45 STACK sc;
46 sc = init_stack();
47
48 //push几个数据
49 stack_push(&sc, 1);
50 stack_push(&sc, 3);
51 stack_push(&sc, 7);
52 stack_push(&sc, 2);
53 stack_push(&sc, 10);
54
55 //遍历
56 trvel_stack(&sc);
57
58 //pop一个数据出来
59 pop(&sc);
60
61 trvel_stack(&sc);
62
63 return 0;
64 }
65
66 /*
67 栈的初始化操作
68 */
69 struct Stack init_stack(){
70 //先定义一个栈
71 STACK sc;
72
73 //初始化栈中的第一个节点(栈低节点),该节点并没有什么实际意义,不存放数据
74 PNODE pbottom = (PNODE)malloc(sizeof(NODE));
75 if (pbottom == NULL) {
76 printf("内存分配失败\n");
77 exit(1);
78 }
79 pbottom->pnext = NULL;
80
81 //将栈顶指向该元素
82 sc.stop = pbottom;
83
84 return sc;
85 }
86
87 /*
88 栈的push操作
89 */
90 void stack_push(STACK *sc, int data){
91 printf("调用了!\n");
92
93 //新创建一个节点
94 PNODE pnew = (PNODE)malloc(sizeof(NODE));
95 printf("%p\n", pnew);
96 if (pnew == NULL) {
97 printf("内存分配失败\n");
98 exit(1);
99 }
100 pnew->data = data;
101
102 //将新节点指向原top节点,将栈的top指向该新节点
103 pnew->pnext = sc->stop;
104 sc->stop = pnew;
105 }
106
107 /*
108 栈的pop操作
109 */
110 int pop(STACK *sc){
111 int res;
112 //判断栈是否为空
113 if (is_empty(sc) == 1){
114 printf("栈为空,不可pop\n");
115 exit(1);
116 } else {
117 PNODE ptmp = sc->stop; //先将需要被pop出的节点存储起来
118 sc->stop = sc->stop->pnext; //将栈的top指向下一个节点
119 res = ptmp->data;
120 free(ptmp); //将地址释放
121 return res;
122 }
123 }
124
125 /*
126 判断栈是否为空的操作
127 */
128 int is_empty(STACK *sc){
129 PNODE p = sc->stop;
130 if (p == NULL || p->pnext == NULL) {
131 return 1; //true表示为空
132 } else {
133 return 0; //false表示非空
134 }
135 }
136
137 /*
138 遍历栈
139 */
140 void trvel_stack(STACK *sc){
141 if (is_empty(sc) == 1) {
142 printf("栈为空哟,无法遍历呢~\n");
143 exit(1);
144 } else {
145 printf("遍历调用了!\n");
146 PNODE ptmp = sc->stop;
147 while (ptmp->pnext != NULL) { //直到最后一个为NULL的节点,该节点就是最后一个节点
148 printf("%d\n", ptmp->data);
149 ptmp = ptmp->pnext;
150 }
151 }
152
153 }