栈、队列、链表

news2024/9/20 4:13:19

基于《啊哈!算法》和《数据结构》(人民邮电出版社)

本博客篇幅较多,读者根据目录选择,不理解的可留言和私信。

栈、队列、链表都是线性结构。

三者都不是结构体、数组这种数据类型,我认为更像是一种算法。

针对问题:

1.相较于数组的优点

2.模板,结合结构体

3.栈、队列、链表之间的结合

4.STL库容器

一、队列

原则:先进先出

        队列,顾名思义就像是买东西排队先到的人先出,我们先看一道《啊哈!算法》上的例子:

解密QQ号:

        “6 3 1 7 5 8 9 2 4”是小哈给小哼的加密数字,规则是:首先将第一个数删除,紧接着将第二个数放到这串数的末尾,再将第三个数删除并将第四个数放到这串数的末尾,再将第五个数删除……直到剩下最后一个数。将最后一个数删除。按照刚才删除的顺序,把这些删除的数连在一起就是小哈的QQ号。

        对于这道题,这些加密数字就满足“先进先出”的原则。解题思路如图:

非队列思路 

         在不知道队列的时候,我的C解题思路如下:(其实也有队列的影子)

#include <stdio.h>

int main()
{
    int q[100]={0,6,3,1,7,5,8,9,2,4};
    int m[10]={0};
    int len=10;
    int i=0;
    for ( i = 1; q[i] != 0; i++)//条件最后一个数被删除
    {
        // printf("%d\n",q[i]);
        if(i%2==1)//删除
        {
           printf("%d",q[i]);
        }
        
        else if(i%2 == 0)//放到数组的末尾
        {
            q[len]=q[i];
            len++;
        }
    }
    // for(i=0;i<len-1;i++)
    // printf("%d",m[i]);
    return 0;
}

        当i为奇数时,则删除就,为偶数时就移到末尾,直到删除到最后一个,及q[i]==0。

        引入队列的定义:队列主体,队首,队尾;队首出队即删除,队尾进队即插入。

队列思路

我认为队列可以更加优雅的完成一些问题。队列的解题思路如下:

#include<stdio.h>
int main()
{
    int q[102]={0,6,3,1,7,5,8,9,2,4};

    int head = 1;
    int tail =10;
    while(head<tail)
    {
        //打印队首并将队首出队
        printf("%d ",q[head]);
        head++;
        
        //先将新队首的数添加到队尾
        q[tail]=q[head];
        tail++;
        //再将队首出队
        head++;
    }

    return 0;
}

现在有九个数,9个数全部放入队列之后,head=1,tail=10(队尾指向的是尾元素的后一位,1-9为有效元素,这样写防止了“假溢出”),此时head和tail之间的数就是队列的有效数,如果要删除一个数的话。就将head++就行了,这样可以保证head和tail之间的数为有效数,及“排队的数”,这样做虽然浪费了一个空间,却节省了大量的空间,这是非常划算的,新增一个数也很简单,把需要增加的数放到队尾即q[tail]之后再tail++就行了。 

定义一个结构体,即可认定为一个新的数据类型:队列,如下:

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

结构体模板

 下面这段代码就是使用结构体来实现队列操作:

#include<stdio.h>

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

int main()
{
    struct queue q;
    int i=0;
    q.head = 1;
    q.tail = 1;
    for ( i = 1; i <= 9; i++)
    {
        scanf("%d",&q.data[q.tail]);
        q.tail++;
    }
    
    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++;
    }

    return 0;
}

 另外C++的STL库已经有队列的实现。

队列的基本操作
q.push(x):将元素 x 放入队尾。
q.pop():将队头元素出队。(只能删除,不能返回队首的值)
q.empty():判断队列是否为空,为空返回 t r u e,否则返回 f a l s e 。(头尾队列重合为空)
q.size():返回队列长度。
q.front():返回队头元素,不出队。(返回的是队首元素的值)
q.back():返回队尾元素。

STL库队列容器

使用STL库的queue容器实现:

#include <bits/stdc++.h>
using namespace std;

int main()
{
    queue<int> q;
    for (int i = 1; i <= 9; i++)
    {
        int x=0;
        scanf("%d",&x);
        q.push(x);
    }
    
    while(q.empty() == 0)    //当队列不为空的时候执行循环
    {
        //打印队首并将队首出队
        printf("%d ",q.front());
        q.pop();

        //先将新队首的数添加到队尾
        int x=q.front();
        q.push(x);
        //再将队首出队
        q.pop();
    }


    return 0;
}

其它还有:优先队列,双向队列。 

扩展: 

       (一)循环队列

        通过取“模”实现。

        假设当前队列分配的最大空间为6,则当队列处于如图(d)所示(队首经过删除得到)的状态时不可再继续插入新的队尾元素,否则会出现溢出现象。但实际上,队列的实际可用空间只用了两个,这称为“假溢出”。是由“队尾入队,队头出队”这种限制造成的。

        一个巧妙的办法是将顺序队列变为一个环状的空间,成为循环队列。如图所示3.13:

        头、尾指针以及队列元素之间的关系不变,只是在循环队列中,头、尾指针“依环状增1”的操作通过模实现。通过取模,头尾指针可以在顺序表空间内以头尾衔接的方式循环移动。

        如图3.14(a)队头位置是q[5](从1-6共六个数),队尾位置是q[5](队头队尾重合),队尾插入一位,通过模运算,q[(5+1)%6]==q[0],即实现循环,又多“开辟”了6-1-2-3-4这五个空间,并且实现了循环。

                那么我们如何判断队列空间是满还是空呢?

        (1)少用一个元素,即当队列空间大小为m时,有m-1个元素就认为是队满。这样判断对空的条件不变,即当头尾指针的值相同时;而当尾指针在循环意义上加1后等于头指针时,则认为队满。如下:

队空:Q.front == Q.rear(一定要记得Q。rear指向的是队尾元素的后一个)

队满:Q.(Q.rear + 1)%m == Q.front

        (2)另设一个标记位,队列本来就是类似算法,灵活性高,大家可以自己定义。

                求循环队列的长度

        (Q.rear-Q.front+m)%m;(前面经过了取模,所以Q.rear的范围为1-m)

        情况d来举例,(4-5+6)%6=5;是J5-J9。

        (二)链队列

        顾名思义,相比前面的线性结构,这里是链式结构实现的队列。(具体实现了解链表便可领会)

                特点:

(1)不需要判断队满(不受最大空间容量的限制)

(2)可以插队

(3)队首为链表的头部

二、栈

原则:后进先出

        栈限定为只能在一端进行插入和删除工作,就像是一个小桶,直径只能放一个球,先放进去的球最后取,后放进去的球先取。如图(来自《啊哈!算法》)

          栈的实现只需要一个一维数组和一个指向栈顶的变量top就行了。我们通过top来对栈进行插入和删除

        我们来看一道例题:解密回文。

        “xyzyx”是一个回文字符串,所谓回文字符串就是指正读和反读均相同的字符序列,如“席主席”、“记书记”、“aha”和“ahaha”均是回文,但“ahah”不是回文,通过栈这个数据结构我们将很容易的判断一个字符串是否为回文。

        栈思路

        假设判断“ahyha”

        先求出字符串长度,如果一个字符串为回文的,那么必将中心对称,中心点‘y’左面的从左向右依次入栈入栈为“a h”,栈顶指向‘h’,然后从栈顶依次出栈与中心点右边的“h a”比较。

#include <stdio.h>
#include <string.h>

int main()
{
    char str[100]={'\0'},s[100]={'\0'};
    int len=0,i=0,next=0,top=0,mid=0;
    gets(str);
    len=strlen(str);
    // 读取字符串和长度

    mid=len/2-1;
    // 如果回文则必将从中间对称,我们取中心点

    for ( i = 0; i <= mid; i++)// 加等于号,预防输入的是两个字符
    {
        s[++top]=str[i];
    }
    // 将mid之前的字符存入栈

    if (len%2==0) // 判断回文为奇数还是偶数
    {
        next=mid+1; //偶数
    }
    else
        next=mid+2; //奇数
    
    for ( i = next; i <= len-1; i++)
    {
        if (str[i]!=s[top])// 判断是否为中心对称
        {
            break;
        }
        top--;
        // 不能简写s[top--],应该先判断为1后再--
    }
    
    // 如果top为0,则说明栈内所有的字符都被一一匹配了
    if(top == 0)
        printf("YES");
    else
        printf("NO");

    
    return 0;
}

结构体模板 

        栈也有结构体模板,解密回文具体思路不再展示。

struct stack
{
    int data[100];    //栈的主体
    int top;        //栈顶      
};

 STL栈容器

使用STL库的stack容器实现:       

#include <bits/stdc++.h>
using namespace std;

int main()
{
    stack<int> s;
    char str[100]={'\0'};
    int len=0,next=0,mid=0;
    gets(str);
    len=strlen(str);
    // 读取字符串和长度

    mid=len/2-1;
    // 如果回文则必将从中间对称,我们取中心点

        for (int i = 0; i <= mid; i++)// 加等于号,预防输入的是两个字符
    {
        s.push(str[i]);
    }
    // 将mid之前的字符存入栈

     if (len%2==0) // 判断回文为奇数还是偶数
        next=mid+1; //偶数
    else
        next=mid+2; //奇数

    for (int i = next; i <= len-1; i++)
    {
        if (str[i]!=s.top())// 判断是否为中心对称
        {
            break;
        }
        s.pop();
        // 不能简写s[top--],应该先判断为1后再--
    }
    
    // 如果top为0,则说明栈内所有的字符都被一一匹配了
    if(s.size() == 0)
        printf("YES");
    else
        printf("NO");

    
    return 0;
}

stack容器和queue容器 成员函数差不多,两种容器均自动管理内存,无容量限制;均不允许遍历操作。

1、s.emplace();                在栈顶构造元素,避免复制或者移动等操作影响性能

2、s.empty();                      判断栈是否为空

3、s.pop();                          从栈顶出栈

4、s.push();                      从栈顶入栈

5、s.size();                         返回栈元素数量

6、s.swap();                       交换堆栈

7、s.top();          访问栈顶元素,返回引用

扩展:

(一)链栈

        链表与栈结合,链式结构的栈。

        特点:

(1)不需要判断栈满(不受最大空间容量的限制)

(2)栈顶为链表的头部        

(二)栈与递归

        栈有一个重要的应用是在程序设计语言中实现递归。递归与栈有很多相似之处

        所谓递归是指,若在一个函数、过程或者数据结构定义的内部又直接(或间接)出现定义本身的应用,则称其是递归的,或者是递归定义的。在以下三种情况,常常使用递归的方法:

1.定义是递归的

 阶乘函数

求解代码(  n!)

long Fact(long n)
{
    if( n == 0) return 1;    // 递归终止条件
    else return n*Fact(n-1); // 递归步骤
}

二阶斐波那契数列

求解代码

long Fib(long n)
{
    if(n==1 || n==2) return 1;      //递归终止条件
    else return Fib(n-1)+Fib(n-2);  //递归步骤
}

计算4!时先计算3!,再进一步分解求解进行求解,这种分解求解的策略叫做“分治法

采取分治法的条件:

(1) 能将一个问题转变成一个新问题,并且新问题与原问题解法相同或相似。不同的仅是处理对象,并且其处理对象更小且变化规律。

(2)可以通过上述转化而使问题简化。

(3)必须有一个明确的递归出口,或者称递归的边界。

“分治法”求解递归问题算法的形式如下:

void p(参数表)
{
    if(递归结束条件成立) 可直接求解;    // 递归终止的条件
    else p(较小的参数);                // 递归步骤
}
2.数据结构是递归的

        链表

3.问题的解法是递归的

        有一类问题,问题本身没有明显的递归结构,但用递归求解比迭代求解更简单,经典如Hanoi(汉诺)塔问题、八皇后问题、迷宫问题等。

        

Hanoi塔问题的递归算法(问题不再赘述)

【算法步骤】

1.如果 n = 1, 则直接将编号为1的圆盘从塔座A移到塔座C,递归结束。

2.否则:

  • 递归,将塔座A上编号为1至n-1的圆盘移到塔座B,塔座C作为辅助塔座;
  • 直接将编号为n的圆盘从塔座A移到塔座C,塔座A为辅助塔座。
  • 递归,将塔座B上编号为1至n-1的圆盘移到塔座C,塔座A作为辅助塔座。
#include <stdio.h>

int m=0;
void move(char A,int n,char C)
{
    printf("%d,%d,%c,%c\n",++m,n,A,C);
}

void Hanoi(int n, char A,char B,char C)
{//将塔座A上的n个圆盘按规格搬到塔座C上,塔座B作为辅助塔座
    if(n == 1) move(A,1,C);
    else
    {
        Hanoi(n-1,A,C,B); //将塔座A上编号为1至n-1的圆盘移到塔座B,塔座C作为辅助塔座;
        move(A,n,C);      //直接将编号为n的圆盘从塔座A移到塔座C,塔座A为辅助塔座。
        Hanoi(n-1,B,A,C); //递归,将塔座B上编号为1至n-1的圆盘移到塔座C,塔座A作为辅助塔座。
    }

}
int main()
{
    char A='A',B='B',C='C';
    Hanoi(2,A,B,C);
    return 0;
}
递归过程和递归工作站

        我们可以发现,当有多个函数构成嵌套调用时,按照“先调用后返回”的原则,上述函数之间的信息传递和控制必须通过“栈”来实现,系统将整个程序运行所需的数据空间安排在一个栈中,没调用一个函数就在栈顶为它分配一个储存空间,每从一个函数退出,就释放它的储存区。如此,当前正运行的函数的数据区必在栈顶。

递归算法的效率分析
        时间复杂度分析

        当一个算法中包含递归调用时,其时间复杂度的分析可以转化为一个递归求解方程,就是数学上求解渐近阶的问题。

        迭代法时求解递归方程的一种常用方法,其基本步骤是迭代地展开递归方程的右端,使之成为一个非递归的和式,然后通过对和式的估计来达到对方程左端的估计。

例如求阶乘函数Fact(4):(来自《数据结构》)

        设Fact(n)的执行时间是T(n)。此递归函数中语句: if( n == 0) return 1 的执行时间是o(1),递归调用Fact(n-1)的执行时间是T(n-1),所以else return n*Fact(n-1)的执行时间是O(1)+T(n-1)。其中,设两数相乘和赋值操作的时间为O(1),则对某常数C、D有如下递归方程:

 采用这种方法计算斐波那契数列和Hanoi塔问题递归算法的时间复杂度,可得到结果均为O(2^n)。

        空间复杂度分析

        S(n) = O(f(n))

        其中,f(n)为“递归工作栈”中工作记录的个数与问题规模n的函数关系。

        斐波那契数列和Hanoi塔问题递归算法的空间复杂度,可得到结果均为O(n)。

  

利用栈将递归转换为非递归的方法

        递归程序在执行时需要系统提供隐式栈这种数据结构来实现,对于一般的递归过程,仿照递归算法执行过程中递归栈的状态变换可直接写出相应的非递归算法,具体实例不再叙述,想要了解的直接可以去试试。

三、链表 

链表是一种递归的数据结构,学会链表的前提是你要懂得指针和结构体。

        有一串已经排好大小的数字“2 3 5 8 9 10 18 26 32”,现在要向这段数字中插入‘6’,使其新的序列仍符合从大到小排列,如果我们用数组来实现这一操作,则需要将8和8后面的数都依次往后挪一位,而对于链表,只需要将‘5’的指针指向‘6’,再将‘6’的指向‘8’就完成了插入。如下

       

        也正因为如此,链表相邻元素的地址可以是不连续的,就像发邮件,我不需要和你挨在一起,只需知道你的邮箱就行。所以链表中,我认为最重要的就是地址

        代码示例

        废话不多说,我们先来理解一个简单点的的链表代码:(大家可以结合注释看一下代码。代码实现的功能是将n个数字存入链表)

#include<stdio.h>
#include<stdlib.h>

struct node //定义链表
{
    int data;
    struct node *next;
};


int main()
{
    struct node *head, *p, *q, *t;
    int i,n,a;
    scanf("%d",&n);
    head =NULL;
    for ( i = 0; i < n; i++)  //将数据存入链表
    {
        scanf("%d",&a);
        p=(struct node *)malloc(sizeof(struct node));  //分配动态内存,给p分配一个地址
        p ->data=a;                                    //将a存入这个地址
        p->next=NULL;                                  //分配下一个节点
        if (head ==NULL)        //如果这是创建的第一个节点,则将头指针指向这个节点,理解为火车头
        {
            head=p;
            printf("head=%d",head->data);
        }
        else                    //不是第一个节点,就与上一个节点连接,就是连接车厢,如果上节车厢为A,这节为B,那么就是把B连接到A
            q->next=p;
            
        q=p;                    //保证下一个车厢连接的是B不是A
        // printf("%d \n",q->data);
        // t=q->next;
        // printf("%d\n",t->data);
    }

    t=head;
    while (t!=NULL)
    {
        printf("%d ",t->data);//从火车头向后跑,把连接的车厢依次输出。
        t=t->next;
    }
    free(p);
    p=NULL;
    return 0;
}

         把这个理解了,链表的“增 删 改 查”就没有什么问题了。要理解最重要的是地址,这些步骤都是围绕将数字存入地址进行的,这几个结构体变量都是只能存储一个节点。链表是通过地址连接的。

结构体模板

struct node //定义链表
{
    int data;
    struct node *next;
};

         结构体只包含两个变量,一个是存储的数据data,另一个就是该节点的地址。多个节点相连接,便构成了链表。

头节点、首元节点、头指针

        这是链表中比较容易混淆的三个概念。

        (1)首元节点是指链表中储存第一个数据元素的结点,

        (2)头结点是首元结点之前附设的一个结点,其指针域指向首元结点。头结点的数据域可以不储存任何信息,也可储存与数据元素类型相同的其它附加信息。

        (3)头指针是指向链表中第一个节点的指针。若链表设有头结点,这头指针指向它,若没有则指向首元结点。

        本代码中,head为头指针,head指向的是首元结点,没有头结点。

前插法与后插法

        同样得到“1 2 3 4”,前插法需要输入“4 3 2 1”,后插法输入“1 2 3 4”.

        如果我们把第一个创建的结点称为第一结点。

        那么前插法便是将结点插入到第一结点的前面,而后插法就是将结点插入到第一结点的后面。

        

        本套代码便是后插法,多了一个尾指针q。

        正确代码

        下面我们展示一套代码往链表中插入‘6’。

#include<stdio.h>
#include<stdlib.h>

struct node //定义链表
{
    int data;
    struct node *next;
};


int main()
{
    struct node *head=NULL, *p=NULL, *q=NULL, *t=NULL;
    int i,n,a;
    scanf("%d",&n);
    head =NULL;
    for ( i = 0; i < n; i++)  //将数据存入链表
    {
        scanf("%d",&a);
        p=(struct node *)malloc(sizeof(struct node));  //分配动态内存,给p分配一个地址
        p ->data=a;                                    //将a存入这个地址
        p->next=NULL;                                  //分配下一个节点
        if (head ==NULL)        //如果这是创建的第一个节点,则将头指针指向这个节点,理解为火车头
        {
            head=p;
        }
        else                    //不是第一个节点,就与上一个节点连接,就是连接车厢,如果上节车厢为A,这节为B,那么就是把B连接到A
            q->next=p;
            
        q=p;                    //保证下一个车厢连接的是B不是A
    }

    t=head;                              
    while(t!=NULL)  //将数据存入链表
    {
        
        if (t->next == NULL || t->next->data > 6 )         //此节点是结尾或下一个节点大于6      
        {
            p=(struct node *)malloc(sizeof(struct node));  //分配动态内存,给p分配一个地址
            p ->data=6;                                    //将6存入这个地址
            //******************* 
            p->next=t->next;                               
            t->next=p;
            //*******************将存储6的节点插入链表
            break;
        }      
        t=t->next;  
    }
    
    t=head;
    while (t!=NULL)
    {
        printf("%d ",t->data);//从火车头向后跑,把连接的车厢依次输出。
        t=t->next;
    }
    
    free(p);
    p=NULL;
    return 0;
}

这段代码与上一段代码就差了一段:(看注释理解)

    t=head;                              
    while(t!=NULL)  //将数据存入链表
    {
        
        if (t->next == NULL || t->next->data > 6 )         //此节点是结尾或下一个节点大于6      
        {
            p=(struct node *)malloc(sizeof(struct node));  //分配动态内存,给p分配一个地址
            p ->data=6;                                    //将6存入这个地址
            //******************* 
            p->next=t->next;                               
            t->next=p;
            //*******************将存储6的节点插入链表
            break;
        }      
        t=t->next;  
    }

 野指针问题

        再写代码的时候出现了一个问题:(大家注意这段代码与前面的差别)

 t=head;                              
    while(t!=NULL)  //将数据存入链表
    {
        
        if ( t->next->data > 6 || t->next == NULL )         //此节点是结尾或下一个节点大于6      
        {
            p=(struct node *)malloc(sizeof(struct node));  //分配动态内存,给p分配一个地址
            p ->data=6;                                    //将6存入这个地址
            //******************* 
            p->next=t->next;                               
            t->next=p;
            //*******************将存储6的节点插入链表
            break;
        }      
        t=t->next;  
    }

        没错!就是if条件的改变,把||两端的条件调换了位置。

        如果大家不能看出来什么问题,不妨试试输入:

情况一:

3

1 2 3 

情况二: 

3

1 2 8 

        看看这两套代码有什么区别。情况一中,两套代码结果一样。 而情况二,第一套代码中正常输入结果,而第二套代码中就没有了结果,代码‘崩’了。

        在解决问题之前,大家首先要知道‘||’这个符号的作用:(A || B),如果A为真,便不会检查B了,情况二中,把“t->next->data > 6”放在了后面,运行中发现“t->next == NULL”为真,便不会执行“t->next->data > 6”了,而第二套代码会先执行“t->next->data > 6”。

        这就会导致野指针的问题出现。与下面这套代码相似:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int *p;
    printf("%d",p);
    printf("这个不会输出!");
    return 0;
}

 造成这个原因就是*p是野指针,它指的是无效内存!具体原因参考博客:【C语言基础】野指针与空指针_野指针和空指针-CSDN博客

        删除6

    t=head; 
    while (t!=NULL)
    {
        if (t->next->data == 6)
        {
            p=t->next;
            t->next=t->next->next;
            free(p);//释放删除节点空间
        }
        t=t->next;
    }

        将6改为7

    t=head; 
    while (t!=NULL) //改
    {
        if (t->data == 6)
        {
            t->data = 7;
        }
        t=t->next;
    }

        查找6在链表的哪一个节点。

    int s=1;
    t=head; 
    while (t!=NULL) //查
    {

        if (t->data == 6)
        {
            printf("在链表的第%d个节点\n",s);
            break;
        }
        s++;
        t=t->next;
    }

拓展

        模拟链表

        使用数组来实现链表,叫做模拟链表。通过两个数组实现,数值data[]存每个数,另一个right[]我认为可以理解为存储的下一个节点的地址,不过是是用序号来模拟地址。

        

        例如,data[1]地址是1,他的right是right[ 1 ]为2,即下一个节点的地址是2;

        我们把“6”插入,只需达到data[10]=6,'6'的地址是10,在5的右边现在的地址right[3]是4,改为10即可,将right[10]存为8的地址‘4’。

示例代码 

#include <stdio.h>

int main()
{
    int data[101]={0},right[101]={0};
    int i=0,n=0,t=0,len=0;
    scanf("%d",&n);  //初始链表的个数
    for ( i = 1; i <= n; i++)
    {
        scanf("%d",&data[i]); // 存入链表每个节点的数据
    }
    len=n;

    for (i = 0; i <=n; i++) //存入链表每个节点的模拟地址
    {
        if (i!= n)
        {
            right[i]=i+1;
        }
        else
            right[i]=0;
    }
    // 直接在数组data末尾增加一个数
    len++;
    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++
        
    }
    
    t=1;
    while (t!=0)
    {
        printf("%d ",data[t]);
        t=right[t];
    }
    
    return 0;
}

循环链表

        与循环队列相似,整个链表形成一个环,由此从表中的任一结点出发均可找到表中其它结点。

        循环链表的操作与单链表一致,差别仅在于:当链表遍历时判别当前指针p是否向表尾结点的终止条件不同。在单链表中,判别条件为p!=NULL或 P->next !=NULL,而循环链表的为p

!=NULL或p->next !=NULL。

       我们还可以将两个或者多个链表连接在一起。将第一个表的尾指针指向第二个表的第一个结点,将第二个尾指针连接在第一链表的第一个结点就构成了一个循环链表。

        p= B->next->next;//B的第一个结点

        B->next = A->next;

        A -> next =p;

        时间复杂度为

双向链表 

        双向链表就是一个结点具有两个指针域,一个指向前结点,一个指向后结点。

struct node //定义链表
{
    int data;
    struct node *next;
    struct node *prior;
};

        双向链表的插入和删除需同时修改两个方向的指针。 

参考文章

C++——STL标准模板库——容器详解——stack+queue_stl栈-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2147828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面试必备!值得收藏!不容错过的100+ 大语言模型面试问题及答案

引言 大语言模型&#xff08;LLMs&#xff09;现在在数据科学、生成式人工智能&#xff08;GenAI&#xff0c;即一种借助机器自动产生新信息的技术&#xff09;和人工智能领域越来越重要。这些复杂的算法提升了人类的技能&#xff0c;并在诸多行业中推动了效率和创新性的提升。…

Windows如何查看已缓存的DNS信息

Windows server 2016如何查看已缓存的DNS信息 在Windows server 2016系统下&#xff0c;如何查看已缓存的DNS信息呢? 1.打开“运行”&#xff0c;输入cmd&#xff0c;点击“确定” 2.在命令行界面输入ipconfig /displaydns&#xff0c;按回车即可查看已缓存的dns信息

9月26日云技术研讨会 | SOA整车EE架构开发流程及工具实施方案

面向服务的架构&#xff08;Service Oriented Architecture, SOA&#xff09;实施需要复杂的基础技术作为支撑&#xff0c;伴随着整车硬件资源的集中化、车载以太网等高速通信技术在车内的部署&#xff0c;将在未来一段时间内成为行业技术研究和市场布局的热点。 近年来&#x…

分享几种方式获取免费精致的Live2d模型

文章目录 1、 Live2D官方示例数据集&#xff08;可免费下载&#xff09;2、模之屋3、unity商店4、直接b站搜索5、youtube6、BOOTH完结 1、 Live2D官方示例数据集&#xff08;可免费下载&#xff09; 官方提供了一些 Live2D实例模型给大家下载使用 地址&#xff1a;https://ww…

房屋租赁系统源码分享:SpringBoot + Vue 免费分享

这是一套使用 SpringBoot 与 Vue 开发的房屋租赁系统源码&#xff0c;站长分析过这套源码&#xff0c;推测其原始版本可能是一个员工管理系统&#xff0c;经过二次开发后&#xff0c;功能被拓展和调整&#xff0c;现已完全适用于房屋租赁业务。 源码说明&#xff1a; 该系统功…

一键生成高级感PPT封面,首推这个在线AI生成PPT软件!

PPT封面怎么做&#xff1f; ppt封面的重要性不言而喻&#xff0c;就像写文章讲究的“凤头”&#xff0c;一个漂亮的PPT封面&#xff0c;可以吸引观众的注意力&#xff0c;让人有意愿驻足下来听你演讲&#xff0c;才会有后面更多的故事发生。 漂亮的ppt封面怎么做&#xff1f;…

dll文件丢失怎么恢复?10种dll修复方法任你选,一次学会!

dll文件丢失怎么恢复&#xff1f;dll文件丢失在多个Windows 版本中都是常见的问题&#xff0c;包括win7/win8/win10和 win11。这类错误通常与一些特定的dll文件有关&#xff0c;比如MSVCR110.DLL、MSVCR71.DLL、d3compiler_43.DLL、LogiLDA.DLL、MSVCP140.DLL、api-ms-win-crt-…

组装电脑-电脑配置

键盘、鼠标&#xff1a;买一百多的机械盘。 主板 电脑台式机主板是计算机最基本的同时也是最重要的部件之一&#xff0c;它在整个计算机系统中扮演着举足轻重的角色。以下是对它的详细介绍&#xff1a; 基础功能&#xff1a; 主板作为计算机的核心部件&#xff0c;负责连接和…

【图像检索】基于颜色模型的图像内容检索,matlab实现

博主简介&#xff1a;matlab图像代码项目合作&#xff08;扣扣&#xff1a;3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于颜色模型的图像内容检索&#xff0c;用matlab实现。 一、案例背景和算法介绍 这…

inBuilder低代码平台新特性推荐-第二十四期

今天给大家带来的是 inBuilder 低代码平台新特性推荐第二十四期 ——表单格式支持流程配置。 场景介绍&#xff1a; 如下图所示&#xff0c;目前支持在流程设计上的不同节点设置表单字段的必填、显隐等属性控制&#xff0c;不必在表单设计上进行配置&#xff0c;从而减少了开…

VS Code远程连接虚拟机

VS Code远程连接虚拟机 1.下载vscode2.打开VS Code下载Remote-SSH插件1.修改相关信息 3.虚拟机检查或安装ssh4.检查虚拟机服务是否安装成功5.开启ssh&#xff0c;并检查是否开启成功 1.下载vscode 2.打开VS Code下载Remote-SSH插件 1.修改相关信息 2. 3.虚拟机检查或安装ssh…

同一个单元格内包含标签和文本框

<!DOCTYPE html> <html> <head> <title>单元格内包含标签和文本框</title> <style> /* 可选的CSS样式&#xff0c;用于美化表格 */ table { width: 50%; /* 设置表格宽度为页面宽度的50% */ border-collapse: collapse; /* 合并…

【JSrpc破解前端加密问题】

目录 一、背景 二、项目介绍 三、JSrpc 处理前端加密步骤 一、背景 解决日常渗透测试、红蓝对抗中的前端密码加密问题&#xff0c;让你的爆破更加丝滑&#xff1b;降低js逆向加密的难度&#xff0c;降低前端加密逻辑分析工作量和难度。 二、项目介绍 运行服务器程序和js脚本…

解锁生命活力密码!帕金森患者的专属锻炼秘籍,让每一步都稳健前行

在这个快节奏的时代&#xff0c;健康成为了我们最宝贵的财富之一。然而&#xff0c;对于帕金森病患者而言&#xff0c;身体的逐渐僵硬、运动能力的下降&#xff0c;似乎给生活按下了减速键。但请相信&#xff0c;科学的锻炼方法&#xff0c;就是那把重启生命活力的钥匙&#xf…

时间复杂度的常用符号+渐进时间复杂度分析

时间复杂度的常用符号 Θ \Theta Θ 如果 f ( n ) Θ ( g ( n ) ) f(n)\Theta(g(n)) f(n)Θ(g(n))&#xff0c;则 f ( n ) f(n) f(n) 与 g ( n ) g(n) g(n) 同阶。&#xff08;阶是指 f ( n ) f(n) f(n) 的指数&#xff0c;比如 n 2 n^2 n2 高于 n n n&#xff09; O O …

OJ在线评测系统 登录页面开发 前端后端联调实现全栈开发

前端 登录页面就是一个让用户输入账号和密码的表单 使用acro组件 先写布局 <a-form-item field"userAccount" label"账号"><a-input v-model"form.userAccount" placeholder"请输入账号" /></a-form-item><a-…

msvcp140.dll丢失如何解决?msvcp140.dll丢失的多种解决方法

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcp140.dll丢失”。这个错误通常会导致某些应用程序无法正常运行&#xff0c;给用户带来很大的困扰。那么&#xff0c;当我们遇到msvcp140.dll丢失的情况时&#xff0c;应该如何解决呢&a…

office2016 增强版 KMS

第一步&#xff1a; 用管理员权限登陆&#xff1a;Windows PowerShell &#xff08;安装最新的 PowerShell&#xff0c;了解新功能和改进&#xff01;https://aka.ms/PSWindows&#xff09; 第二步&#xff1a; C:\Windows\system32> cd C:\Program Files\Microsoft Off…

表情包创作、取图小程序端(带流量主)

小程序永久免费&#xff0c;无任何广告&#xff0c;无任何违规功能&#xff01; 小程序具备以下功能有&#xff1a; 支持创作者加入 支持在线制作表情包 使用说明 表情包必备工具&#xff0c;一款专属于你的制作表情包工具&#xff0c;斗图必备神器

macOS平台编译MAVSDK源码生成mavsdk库与mavsdk_server服务可执行文件

克隆源码: 克隆命令 git clone https://github.com/mavlink/MAVSDK.git --recursive 克隆成功如下: 生成makefile (只生成mavsdk库) cmake -Bbuild/default -DCMAKE_BUILD_TYPE=Debug -H. 指定安装目录与生成目录: cmake -Bbuild/macos -DCMAKE_BUILD_TYPE=Debug -…