栈结构

截屏2020-08-25 下午2.03.22.png

队列结构

截屏2020-08-25 下午2.04.36.png

链式栈结构

截屏2020-08-25 下午2.05.52.png
入栈
截屏2020-08-25 下午2.07.42.png
出栈
截屏2020-08-25 下午2.08.18.png

顺序栈的实现

准备条件

  1. #define OK 1
  2. #define ERROR 0
  3. #define TRUE 1
  4. #define FALSE 0
  5. #define MAXSIZE 20 /* 存储空间初始分配量 */
  6. typedef int Status;
  7. typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */
  8. /* 顺序栈结构 */
  9. typedef struct
  10. {
  11. SElemType data[MAXSIZE];
  12. int top; /* 用于栈顶指针 */
  13. }SqStack;

1. 构建一个空栈

  1. Status InitStack(SqStack *S){
  2. S->top = -1;
  3. return OK;
  4. }

2. 将栈置空

  1. Status ClearStack(SqStack *S){
  2. //疑问: 将栈置空,需要将顺序栈的元素都清空吗?
  3. //不需要,只需要修改top标签就可以了.
  4. S->top = -1;
  5. return OK;
  6. }

3. 判断顺序栈是否为空

  1. Status StackEmpty(SqStack S){
  2. if (S.top == -1)
  3. return TRUE;
  4. else
  5. return FALSE;
  6. }

4. 返回栈的长度

  1. int StackLength(SqStack S){
  2. return S.top + 1;
  3. }

5. 获取栈顶

  1. Status GetTop(SqStack S,SElemType *e){
  2. if (S.top == -1)
  3. return ERROR;
  4. else
  5. *e = S.data[S.top];
  6. return OK;
  7. }

6. 插入元素e为新的栈顶元素

  1. Status PushData(SqStack *S, SElemType e){
  2. //栈已满
  3. if (S->top == MAXSIZE -1) {
  4. return ERROR;
  5. }
  6. //栈顶指针+1;
  7. S->top ++;
  8. //将新插入的元素赋值给栈顶空间
  9. S->data[S->top] = e;
  10. return OK;
  11. }

6. 删除S栈顶元素并且用e带回

  1. Status Pop(SqStack *S,SElemType *e){
  2. //空栈,则返回error;
  3. if (S->top == -1) {
  4. return ERROR;
  5. }
  6. //将要删除的栈顶元素赋值给e
  7. *e = S->data[S->top];
  8. //栈顶指针--;
  9. S->top--;
  10. return OK;
  11. }

8. 从栈底到栈顶一次对战中的元素打印

  1. Status StackTraverse(SqStack S){
  2. int i = 0;
  3. printf("此栈中所有元素");
  4. while (i<=S.top) {
  5. printf("%d ",S.data[i++]);
  6. }
  7. printf("\n");
  8. return OK;
  9. }

9. man函数调用

  1. int main(int argc, const char * argv[]) {
  2. // insert code here...
  3. printf("顺序栈的表示与实现!\n");
  4. SqStack S;
  5. int e;
  6. if (InitStack(&S) == OK) {
  7. for (int j = 1 ; j < 10; j++) {
  8. PushData(&S, j);
  9. }
  10. }
  11. printf("顺序栈中元素为:\n");
  12. StackTraverse(S);
  13. Pop(&S, &e);
  14. printf("弹出栈顶元素为: %d\n",e);
  15. StackTraverse(S);
  16. printf("是否为空栈:%d\n",StackEmpty(S));
  17. GetTop(S, &e);
  18. printf("栈顶元素:%d \n栈长度:%d\n",e,StackLength(S));
  19. ClearStack(&S);
  20. printf("是否已经清空栈 %d, 栈长度为:%d\n",StackEmpty(S),StackLength(S));
  21. return 0;
  22. }

链式栈的实现

准备条件

  1. #define OK 1
  2. #define ERROR 0
  3. #define TRUE 1
  4. #define FALSE 0
  5. #define MAXSIZE 20 /* 存储空间初始分配量 */
  6. typedef int Status;
  7. typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */
  8. /* 链栈结构 */
  9. typedef struct StackNode
  10. {
  11. SElemType data;
  12. struct StackNode *next;
  13. }StackNode,*LinkStackPtr;
  14. typedef struct
  15. {
  16. LinkStackPtr top;
  17. int count;
  18. }LinkStack;

代码实现

  1. /*5.1 构造一个空栈S */
  2. Status InitStack(LinkStack *S)
  3. {
  4. S->top=NULL;
  5. S->count=0;
  6. return OK;
  7. }
  8. /*5.2 把链栈S置为空栈*/
  9. Status ClearStack(LinkStack *S){
  10. LinkStackPtr p,q;
  11. p = S->top;
  12. while (p) {
  13. q = p;
  14. p = p->next;
  15. free(q);
  16. }
  17. S->count = 0;
  18. return OK;
  19. }
  20. /*5.3 若栈S为空栈,则返回TRUE, 否则返回FALSE*/
  21. Status StackEmpty(LinkStack S){
  22. if (S.count == 0)
  23. return TRUE;
  24. else
  25. return FALSE;
  26. }
  27. /*5.4 返回S的元素个数,即栈的长度*/
  28. int StackLength(LinkStack S){
  29. return S.count;
  30. }
  31. /*5.5 若链栈S不为空,则用e返回栈顶元素,并返回OK ,否则返回ERROR*/
  32. Status GetTop(LinkStack S,SElemType *e){
  33. if(S.top == NULL)
  34. return ERROR;
  35. else
  36. *e = S.top->data;
  37. return OK;
  38. }
  39. /*5.6 插入元素e到链栈S (成为栈顶新元素)*/
  40. Status Push(LinkStack *S, SElemType e){
  41. //创建新结点temp
  42. LinkStackPtr temp = (LinkStackPtr)malloc(sizeof(StackNode));
  43. //赋值
  44. temp->data = e;
  45. //把当前的栈顶元素赋值给新结点的直接后继, 参考图例第①步骤;
  46. temp->next = S->top;
  47. //将新结点temp 赋值给栈顶指针,参考图例第②步骤;
  48. S->top = temp;
  49. S->count++;
  50. return OK;
  51. }
  52. /*5.7 若栈不为空,则删除S的栈顶元素,用e返回其值. 并返回OK,否则返回ERROR*/
  53. Status Pop(LinkStack *S,SElemType *e){
  54. LinkStackPtr p;
  55. if (StackEmpty(*S)) {
  56. return ERROR;
  57. }
  58. //将栈顶元素赋值给*e
  59. *e = S->top->data;
  60. //将栈顶结点赋值给p,参考图例①
  61. p = S->top;
  62. //使得栈顶指针下移一位, 指向后一结点. 参考图例②
  63. S->top= S->top->next;
  64. //释放p
  65. free(p);
  66. //个数--
  67. S->count--;
  68. return OK;
  69. }
  70. /*5.8 遍历链栈*/
  71. Status StackTraverse(LinkStack S){
  72. LinkStackPtr p;
  73. p = S.top;
  74. while (p) {
  75. printf("%d ",p->data);
  76. p = p->next;
  77. }
  78. printf("\n");
  79. return OK;
  80. }
  81. int main(int argc, const char * argv[]) {
  82. // insert code here...
  83. printf("链栈定义与实现\n");
  84. int j;
  85. LinkStack s;
  86. int e;
  87. if(InitStack(&s)==OK)
  88. for(j=1;j<=10;j++)
  89. Push(&s,j);
  90. printf("栈中元素依次为:");
  91. StackTraverse(s);
  92. Pop(&s,&e);
  93. printf("弹出的栈顶元素 e=%d\n",e);
  94. StackTraverse(s);
  95. printf("栈空否:%d(1:空 0:否)\n",StackEmpty(s));
  96. GetTop(s,&e);
  97. printf("栈顶元素 e=%d 栈的长度为%d\n",e,StackLength(s));
  98. ClearStack(&s);
  99. printf("清空栈后,栈空否:%d(1:空 0:否)\n",StackEmpty(s));
  100. return 0;
  101. }

递归算法

定义

若一个函数,过程或数据结构定义的内部又直接(或间接)出现定义本身的应用;则称他们是递归的,
对于负责的问题,比如汉诺塔问题,若能够分解成几个简单且解法相同的或类似的子问题来求解,便称为递归求解.

例如:在求解4!时先求解3!,然后再进一步分解进行求解,这种求解方式叫做”分治法”
满足条件:

  • 能将一个问题转换变成一个小问题,而新问题和原来问题解法相同或类同,不同的是仅仅是处理的对象,并且这些处理更小且变化有规律的
  • 可以通过上述转化使得问题简化
  • 必须有一个明确的递归出口,或称为递归边界
    1. void p(参数表){
    2. if(递归结束条件成⽴) 可直接求解;
    3. //递归终⽌条件
    4. else p(较⼩的参数); //递归步骤
    5. }
    数据结构是递归的
    其数据结构本身具有递归的特性. 例如,对于链表,其结点LNode的定义由数据域data 和指针域next 组成,⽽指针域next是⼀种指向LNode类 型的指针,即LNode的定义中⼜⽤到了其⾃身. 所以链表是⼀种递归的数据结构;
    1. void TraverseList(LinkList p){
    2. //递归终⽌
    3. if(p == NULL) return;
    4. else{
    5. //输出当前结点的数据域
    6. printf("%d",p->data);
    7. //p指向后继结点继续递归
    8. TraverseList(p->next);
    9. }
    10. }

    栈与递归的关系

    在⾼级语⾔的程序中,调⽤函数和被调⽤的函数之间的链接与信息交换都是通过栈来进⾏的.
    **
    通常,当在⼀个函数的运⾏期间调⽤另⼀个函数时, 在运⾏被调⽤函数之前, 系统需要先完成3件事情:
    1. 将所有的实参,返回地址等信息调⽤传递被调⽤函数保存;
    2. 为被调⽤函数的局部变量分配存储空间
    3. 将控制转移到被调函数⼊⼝;

⽽从被调⽤函数返回调⽤函数之前,系统同样需要完成3件事:
1. 保存被调⽤函数的计算结果;
2. 释放被调⽤函数的数据区
3. 依照被调⽤函数保存的返回地址将控制移动到调⽤函数.
当多个函数构成嵌套调⽤时, 按照”先调⽤后返回”的原则, 上述函数之间的信息传递和控制转移必须通
过”栈”来实现. 即系统将整个程序运⾏时的所需要的数据空间都安排在⼀个栈中, 每当调⽤⼀个函数时,就在
它的栈顶分配⼀个存储区. 每当这个函数退出时,就释放它的存储区.则当前运⾏时的函数的数据区必在栈顶.

在主函数main中调⽤函数first, ⽽在函数first ⼜嵌套调⽤了second 函数. 则,当我们执⾏当前在执⾏的firt
函数时,则栈空间⾥保存了这些信息; ⽽当我们执⾏second则图(b)保存了这些信息.
001.png
⼀个递归函数的运⾏过程类似多个函数嵌套调⽤; 只是调⽤函数和被调⽤函数是同⼀个函数. 因此, 和每次
调⽤相关的⼀个重要概念是递归函数运⾏的”层次”. 假设调⽤该递归函数的主函数为第0层, 则从主函数调
⽤递归函数进⼊第1层, 从第i层递归调⽤本函数为进⼊下⼀层.即第i+1层. 反正退出第i层递归应返回上⼀层,
即第i-1层.
为了保证递归函数正确执⾏, 系统需要设⽴⼀个”递归⼯作栈”作为整个递归函数运⾏期间使⽤的数据存储
区. 每⼀层递归所需信息构成⼀个⼯作记录, 其中包括所有的实参,所有的局部变量以及上⼀层的返回地址.
每进⼊⼀层递归, 就产⽣⼀个新的⼯作记录压⼊栈顶. 每退出⼀个递归,就从栈顶弹出⼀个⼯作记录, 则当前
执⾏层的⼯作记录必须是递归⼯作栈栈顶的⼯作记录, 称为”活动记录”.

  1. int m = 0;
  2. void moves(char X,int n,char Y){
  3. m++;
  4. printf("%d: from %c ——> %c \n",n,X,Y);
  5. }
  6. //n为当前盘子编号. ABC为塔盘
  7. void Hanoi(int n ,char A,char B,char C){
  8. //目标: 将塔盘A上的圆盘按规则移动到塔盘C上,B作为辅助塔盘;
  9. //将编号为1的圆盘从A移动到C上
  10. if(n==1) moves(A, 1, C);
  11. else
  12. {
  13. //将塔盘A上的编号为1至n-1的圆盘移动到塔盘B上,C作为辅助塔;
  14. Hanoi(n-1, A, C, B);
  15. //将编号为n的圆盘从A移动到C上;
  16. moves(A, n, C);
  17. //将塔盘B上的编号为1至n-1的圆盘移动到塔盘C上,A作为辅助塔;
  18. Hanoi(n-1, B, A, C);
  19. }
  20. }
  21. int main(int argc, const char * argv[]) {
  22. // insert code here...
  23. printf("Hanoi 塔问题\n");
  24. Hanoi(3, 'A', 'B', 'C');
  25. printf("盘子数量为3:一共实现搬到次数:%d\n",m);
  26. Hanoi(4, 'A', 'B', 'C');
  27. printf("盘子数量为3:一共实现搬到次数:%d\n",m);
  28. return 0;
  29. }

斐波那锲

  1. int Fbi(int i){
  2. if(i<2)
  3. return i == 0?0:1;
  4. return Fbi(i-1)+Fbi(i-2);
  5. }
  6. int main(int argc, const char * argv[]) {
  7. // insert code here...
  8. printf("斐波拉契数列!\n");
  9. // 1 1 2 3 5 8 13 21 34 55 89 144
  10. for (int i =0; i < 10; i++) {
  11. printf("%d ",Fbi(i));
  12. }
  13. printf("\n");
  14. return 0;
  15. }

补充

栈溢出问题:
如果设计方案为下面这种,到d的时候在插入栈已经满了就出出现溢出问题,为了避免这样设计使用了下面这种闭环的设计方案。
100.png
101.png

102.png
103.png