算法学习2_队列,栈,链表

队列

解密QQ号

新学期开始了,小哈是小哼的新同桌(小哈是个小美女哦~),小哼向小哈询问 QQ 号, 小哈当然不会直接告诉小哼啦,原因嘛你懂的。所以小哈给了小哼一串加密过的数字,同时 小哈也告诉了小哼解密规则。
规则是这样的:
首先将第 1 个数删除,紧接着将第 2 个数放到 这串数的末尾,再将第 3 个数删除并将第 4 个数放到这串数的末尾,再将第 5 个数删除…… 直到剩下最后一个数,将最后一个数也删除。按照刚才删除的顺序,把这些删除的数连在一 起就是小哈的 QQ 啦。现在你来帮帮小哼吧。小哈给小哼加密过的一串数是“6 3 1 7 5 8 9 2 4”。

分析:

正确qq号 : 615947283
现在有 9 个数,9 个数全部放入队列之后 head=1;tail=10;此时 head 和 tail 之间的数就是
目前队列中“有效”的数。如果要删除一个数的话,就将 head++(出队)就 OK 了,这样仍然可以保 持 head 和 tail 之间的数为目前队列中“有效”的数。这样做虽然浪费了一个空间,却节省了 大量的时间,这是非常划算的。新增加一个数也很简单,把需要增加的数放到队尾即 q[tail] 之后再 tail++(入队)就 OK 啦。

《算法学习2_队列,栈,链表》 QQ20180614-163923.png

下面是数组代替队列

#include <stdio.h>
int main(int argc, const char * argv[]) {
  int q[102] = {6,3,1,7,5,8,9,2,4} ,head,tail; // 头下标,尾下
    head = 0; // head指向第一个元素
    tail = 9; // 队列里面有九个元素,tail 指向最后一个
    
    while (head < tail) { // 当队列不为空的时候执行该方法
        
        // 打印队首,
        printf("%d",q[head]);
        head ++; // 并将队首移除队列
        
        // 将新的队首移动到队列末尾
        q[tail] = q[head];
        tail ++;
        // 再将队首移除队列
        head ++;
        
    }
    getchar();
return;
}
  • 队列
    现在我们再来总结一下队列的概念。队列是一种特 殊的线性结构,它只允许在队列的首部(head)进行删除操作,这称为“出队”,而在队列 的尾部(tail)进行插入操作,这称为“入队”。当队列中没有元素时(即 head==tail),称为 空队列。在我们的日常生活中有很多情况都符合队列的特性。比如我们之前提到过的买票, 每个排队买票的窗口就是一个队列。在这个队列当中,新来的人总是站在队列的最后面,来 得越早的人越靠前,也就越早能买到票,就是先来的人先服务,我们称为“先进先出”(First In First Out,FIFO)原则。
    队列将是我们今后学习广度优先搜索以及队列优化的 Bellman-Ford 最短路算法的核心 数据结构。所以现在将队列的三个基本元素(一个数组,两个变量)封装为一个结构体类型, 如下。

队列一般放在main方法外面

struct queue
{
int data[100];//队列的主体,用来存储内容 int head;//队首
int tail;//队尾
};

下面是结构体来实现的队列解密

struct queue
{
int data[100];//队列的主体,用来存储内容 int head;//队首
int tail;//队尾
};

int main(int argc, const char * argv[]) {
  // 结构体的队列操作

    struct queue q;
    int i;
    int qq[12] = {6,3,1,7,5,8,9,2,4};
    // 初始化队列
    q.head = 0;
    q.tail = 0;
    
    for (i = 0; i < 9;i++) {
        printf("输入一个数:%d\n",qq[i]); // 分别输入 6,3,1,7,5,8,9,2,4
        //scanf("%d",&q.data[q.tail]);
        q.data[q.tail] = qq[i];
        q.tail++;
    }
    printf("qq号是 :");
    while (q.head < q.tail) {
        printf("%d",q.data[q.head]);
        q.head ++;  
        q.data[q.tail] = q.data[q.head]; // 新队首 移动到队尾
        q.tail ++;
        q.head ++ ;
    }
    // 打印得到的值是 615947283
    getchar();

return;
}

《算法学习2_队列,栈,链表》 QQ20180614-164502.png

解密回文– 栈 后进先出 LIFO (last in first out)

还有一种是后进先出的数据 结构,它叫做栈。栈限定为只能在一端进行插入和删除操作。比如说有一个小桶,小桶的直 径只能放一个小球,我们现在小桶内依次放入 2、1、3 号小球。假如你现在需要拿出 2 号小球, 那就必须先将 3 号小球拿出,再拿出 1 号小球,最后才能将 2 号小球拿出来。在刚才取小球的 过程中,我们最先放进去的小球最后才能拿出来,最后放进去的小球却可以最先拿出来。

生活中也有一些例子,列如吃薯片,要把上面的吃了,才可以拿出最下面的一片

栈的实现 也很简单,只需要一个一维数组和一个指向栈顶的变量 top 就可以了。我们通过 top 来对栈 进行插入和删除操作。

栈究竟有哪些作用呢?我们来看一个例子。“xyzyx”是一个回文字符串,所谓回文字符 串就是指正读反读均相同的字符序列,如“席主席”、“记书记”、“aha”和“ahaha”均是回 文,但“ahah”不是回文。通过栈这个数据结构我们将很容易判断一个字符串是否为回文。
首先我们需要读取这行字符串,并求出这个字符串的长度。

#include <stdio.h>
#include <string.h>
int main(int argc, const char * argv[]) {
    char a[101], s[101];
    int i ,len ,mid,next,top;
    printf("请输入一行字符串:");
    gets(a); // 读入一行的字符串
    len = (int)strlen(a); // 求字符串的长度

    printf("len ===%d   a == %s\n",len,a);
    mid = len/2 - 1; // 求字符串的中点
    
    top = 0 ; // 初始化栈
    
    // 将mid前的字符串依次入栈
    for (i = 0; i <= mid; i++) {
        top ++;
        s[top] = a[i];
        //        s[++top]  = a[i];  // 这个是上面的简写
    }
    
    // 判断是奇数还是偶数
    if (len%2 == 0) {
        // 偶数 
        next = mid + 1;  // next mid右边的数的下标 abccba  (mid = 6/2 - 1 = 2)  (next = 2 + 1 = 3) ,下标为3就是第二个c
    }else{
        // 奇数
        next  = mid + 2; // next mid右边的数的下标 abcba  (mid = 5/2 - 1 = 1)  (next = 1 + 1 = 2) ,下标为2就是c
    }
    
    // 开始匹配
    for (i = next; i <= len-1; i ++) {
        if (a[i] != s[top]) {
            break;
        }
        top--;
    }
    
    // 如果top值 等于 0 ,说明所有字符都被一一匹配了
        
    if (top == 0) printf("YES");
    else printf("NO");
    getchar();
return;
}

输入一个回文如下1234321

《算法学习2_队列,栈,链表》 QQ20180614-170650.png

链表

在存储一大波数的时候,我们通常使用的是数组,但有时候数组显得不够灵活,比如下 面这个例子。
有一串已经从小到大排好序的数 2 3 5 8 9 10 18 26 32。现需要往这串数中插入 6 使其得 到的新序列仍符合从小到大排列。如我们使用数组来实现这一操作,则需要将 8 和 8 后面的 数都依次往后挪一位,如图下

《算法学习2_队列,栈,链表》 QQ20180614-171310.png

链表就是下面这种情况

《算法学习2_队列,栈,链表》 QQ20180614-171327.png

那么如何实现链表呢?在 C 语言中可以使用指针和动态分配内存函数 malloc 来实现。

/***
 每一个结点都由两个部分组成。左边的部分用来存放具体的数值,那么用一个整型变量 就可以;
右边的部分需要存储下一个结点的地址,可以用指针来实现(也称为后继指针)。
 这里我们定义一个结构体类型来存储这个结点,如下。
 ***/
struct node {
    int data;
    struct node *next;
};

如何建立链表呢?首先我们需要一个头指针 head 指向链表的最开始。当链表还没有建 立的时候头指针 head 为空(也可以理解为指向空结点)。
下面是使用链表插入数据的一个列子

#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char * argv[]) {
 int a,i,n;
    struct node *head,*p = NULL,*q = NULL,*t = NULL;
    head = NULL; // 头指针初始为空
    printf("输入数字n,代表之后需要输入n个数:");
    scanf("%d",&n);
    for (i = 0; i < n; i ++) {  // 循环读入n个数
        printf("输入一个链表节点数据:");
        scanf("%d",&a);
        // 动态申请空间,用来存放一个节点,并用临时指针p指向这个节点
        p = (struct node*)malloc(sizeof(struct node));

        p->data = a;  // 将数据存储到当前的·data域里面
        p->next = NULL; // 设置当前的后继指针指向空,也就是当前节点的下一个节点为空
        if (head == NULL) {
            head = p;  // 如果是第一个节点,将头指针指向这个节点
        }
        else
        {
            q->next = p; // 如果不是第一次创建的节点,则将上一个的后继指针指向当前节点
        }
        q = p; // 指针q也指向当前节点

    }

    t = head;
    printf("插入一个数:");
    scanf("%d",&a);
    while (t != NULL) {  // 当没有达到链表尾部得时候循环
        if (t->next->data > a) {   // 如果当前节点的下一个节点值大于待插入得数,将插入到中间存放新增节点
            p = (struct node *)malloc(sizeof(struct node));
            p->data = a;
            p->next = t->next;  // 新增节点的后继指针指向当前节点的后继指针指向的节点
            t->next = p; // 当前节点的后继指针指向新增节点
            break;  // 插入完毕,退出循环
        }
        t = t->next;  // 继续下一个节点
    }
    
    // 输出链表里面的所有数
    t = head;
    while (t != NULL) {
        printf("%d ",t->data);
        t = t->next;  // 继续下一个节点
    }
    free(p);
    //   初始9个数 1 4 6 7 8 9 12 33 45 ,插入 11  得到 1 4 6 7 8 9 11 12 33 45
    getchar();
return;
}

如果你觉得上面一个链表看不懂的话,可以看一下下面的模拟链表

模拟链表是由两个数组组成的
第一个整型数组 data 是用来存放序列中具体数字的,
另外一个整型 数组 right 是用来存放当前序列中每一个元素右边的元素在数组 data 中位置的(作用相当代替链表里面的指针 struct node *next;)
具体如下图

《算法学习2_队列,栈,链表》 QQ20180614-173031.png

现在需要在 8 前面插入一个 6,只需将 6 直接存放在数组 data 的末尾即 data[10]=6。接下 来只需要将 right[3]改为 10,表示新序列中 3 号元素右边的元素存放在 data[10]中。再将 right[10] 改为4,表示新序列中10号元素右边的元素存放在data[4]中。这样我们通过right数组就可以 从头到尾遍历整个序列了(序列的每个元素的值存放在对应的数组 data 中)

《算法学习2_队列,栈,链表》 QQ20180614-173039.png

#include <stdio.h>
int main(int argc, const char * argv[]) {
    /*
     第一个整型数组 data 是用来存放序列中具体数字的,另外一个整型 数组 right 是用来存放当前序列中每一个元素右边的元素在数组 data 中位置的
     **/
    

    int data [101], right[101];
    int i,n,t,len;   // t 为下一个节点 len 为长度
    // 读入已有得数
    printf("输入要读的个数:");
    scanf("%d",&n);
    for (i = 1; i < n+1; i++) {
        printf("输入一个链表节点数据:");
        scanf("%d",&data[i]);
    }
    len = n;
    
    // 初始化数组right
    for (i = 1; i < n+1; i ++) {
        if (i != n) {
            right[i]  = i +1;
        }
        else
        {
            right[i] = 0;
        }
    }
    
    /// 直接在数组末尾添加一个数
    len ++; // 长度加一
    printf("插入一个数:");
    scanf("%d",&data[len]);
    
    // 重链表头部开始便利
    t = 1;
    while (t != 0) {
        
        if (data[right[t]] > data[len]) {  //如果当前节点的下一个节点值大于待插入得数,将插入到中间存放新增节点
            right[len] = right[t]; // 新插入的下一个节点 等于 当前节点的下一个节点编号
            right[t] = len;   // 当前节点的下一个编号,就是新插入的编号
            break;
        }
        t = right[t];
    }
    
    // 输出链表所有得数
    t = 1;
    while (t != 0) {
        printf("%d ",data[t]);
        t=right[t];
    }
    
    getchar();
return;
}

本人也是刚刚学习,如有错误,请多多指正。
本文部分内容参考 【啊哈!算法】这本书
代码例子

    原文作者:hhjdk
    原文地址: https://www.jianshu.com/p/609a001dbf1e
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞