《啊哈算法》第二章栈,队列,链表(解析+代码)

news2024/11/17 3:30:31

从无到有学算法 

吾日三省吾身,今天有写代码乎?🙄

🙄抠门渣男语录:我的果汁分你一半(月亮弯弯 绵绵绵绵缠缠) - 李金源 - 单曲 - 网易云音乐

千年之后的你在哪里:星月神话 - 我觉得我还能再吃点 - 单曲 - 网易云音乐 

目录

第一节 解密QQ号--队列 

第二节 解密回文--栈

第三节 纸牌游戏--小猫钓鱼

题目

思路 + 代码

 完整代码

输入输出

第四节 链表 

基础概念

实现链表

 完整代码1

插入数据

 完整代码2

第五节 模拟链表


先补充个理解:

 1,栈中的“先进后出,后进先出”什么意思

栈类似于弹匣,就像装子弹压弹进弹匣一样,一粒一粒压进去,但是退子弹是从最上面开始的,最先压进去的最后弹出来,最后一个压进去的第一个弹出来,这就是“先进后出”,也叫“后进先出

2,栈的定义

 栈就是数据暂时存储的地方,所以才有进栈出栈的说法

3,栈与队列的区别

栈是把子弹子弹压进弹匣,后进的先出;队列就像排队,你第一个排,那就第一个轮到你,这就是先进先出,也叫后进后出。dfs(深度优先搜索)应用,而bfs(广度优先搜索)应用队列。栈占用空间小,而堆中的bfs常用于最短路

4,栈在计算机领域的解释

栈作为一种数据结构,是只能在一端进行插入和删除的特殊线性表,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始读。允许进行插入和删除操作的一段称为栈顶(top),另一端称为栈底(bottom)。栈底固定,而栈顶浮动;栈中元素个数为0时成为空栈。插入数据称为进栈(PUSH),删除数据称为退栈(POP),栈也称后进先出表。栈在函数调用时可以可以存储断点,做递归时要用到栈!

5,堆和栈的区别

快捷但自由度小,堆过程长但自由度大

第一节 解密QQ号--队列 

介绍

队列是一种特殊的线性结构,遵循“先进先出”,First In First Out,FIFO原则

它是BFS(Breadth First Search广度优先搜索)和队列优化的Bellman-Ford最短路算法的核心数据结构

比如排队买票,打电话中的人工服务,都是队列的例子,来的越早的人,越早买到票并离开

队首(head)删除数据,称之为“出队

队尾(tail)插入数据,称之为“入队

当队列中没有元素时(head == tail),称为空队列

题目

小哼问小哈QQ号是多少,小哈是个大美女,小哈给了他一串加密后的数字,解密规则是:

先将第1个数删除,然后把第2个数放到这串数末尾,再将第3个数删除,并把第4个数放到末尾......

直到剩下最后一个数,把最后一个数也删除。把删除的数连在一起就是小哈的QQ号了。小哈给他加密过的一串数是“920430714”,求小哈的QQ号是多少?

解析

队首删除一个数的操作是:head++;

队尾增加一个数的操作是:q[tail] = x; tail++;

现在有9个数,把9个数放入队列后,head == 1,tail == 10,此时head和tail之间的数就是目前队列中“有效”的数

代码1

#include<iostream>
using namespace std;
int main()
{
    int q[102] = {0, 9, 2, 0, 4, 3, 0, 7, 1, 4}; //q[0]是额外加上的
    int head = 1, tail = 10; //head指向队首,tail指向队尾下一个位置
    while(head < tail) {//队列不为空
        cout<<q[head]; //打印队首
        head++; //队首出队
        q[tail] = q[head]; //新的队首添加到队尾
        tail++;
        head++; //新队首出队
    }
    return 0;
}
903744102

代码2

将队列三个基本元素(一个数组,两个变量)封装为一个结构体类型

代码臃肿(本题不必用结构体),只是为了敲多一遍加深印象

#include<iostream>
using namespace std;
struct que
{
    int a[100]; //队列主体,存储数据
    int head; //队首
    int tail; //队尾
};
int main()
{
    struct que q;
    q.head = 1;
    q.tail = 1;
    for(int i = 1; i <= 9; ++i) {
        cin>>q.a[q.tail]; //在队尾插入数据
        q.tail++;
    }
    while(q.head < q.tail) {//队列不为空
        cout<<q.a[q.head]; //打印队首
        q.head++; //队首出队
        q.a[q.tail] = q.a[q.head]; //新的队首添加到队尾
        q.tail++;
        q.head++; //新队首出队
    }
    return 0;
}
9 2 0 4 3 0 7 1 4
903744102

第二节 解密回文--栈

队列先进先出,栈后进先出,生活中的例子有:

1,吃桶装薯片,得先吃完前面的才能吃到最后一片

2,浏览网页退回之前某个网页,需要一步步点击后退键

3,给弹夹装子弹,最后装入的子弹是第一个被打出去的,或者说第一个被退出来的

4,又比如一个小桶,小桶的直径只能放入一个球,依次放入2,1,3号小球,想取出2号球就得先将3号取出来,再将1号取出来,最后才能取最先放进去的2号小球

 下面我们用栈的思路判断回文:

只需要一个一维数组a[110]保存读入的数据

一个一维数组s[110]作为栈的主体和一个指向栈顶的变量top就可以

初始化top = 0

入栈的操作:s[++top] = a[i];

通过strlen(a)得到字符串长度len,mid = len / 2 - 1;

代码第14行 s[++top] = a[i]; 为什么用++top呢,为了使后续top = 1(最后一个字符)后,再 top--,即top = 0时可判断为YES

第8行 cin.get(a, 110);  等价于 gets(a),都可读入字符数组的字符串,不过后者头文件为#include<cstdio>,前者头文件为#include<cstring>

#include<iostream>
#include<cstring> //strlen(), cin.get()
using namespace std;
int main()
{
    char a[110], s[110];
    int len, mid, next, top;
    cin.get(a, 110); //读入字符串
    len = strlen(a); //求长度
    mid = len / 2 - 1;

    top = 0;
    for(int i = 0; i <= mid; ++i)
        s[++top] = a[i]; //mid前的字符入栈
    if(len % 2 == 0) next = mid + 1;
    else next = mid + 2; //奇偶匹配的起始下标不一样

    //开始匹配
    for(int i = next; i <= len - 1; ++i) {
        if(a[i] != s[top])
            break;
        top--;
    }
    if(top == 0) cout<<"YES"<<endl;
    else cout<<"NO"<<endl;
    return 0;
}
aininia
YES

nihuai
NO

下面是不用栈的思路,它需要一个数组a和两个变量left, right

#include<iostream>
#include<cstring> //cin.get(), strlen()
using namespace std;
char a[666];
bool repeat(int left, int right) //判断回文
{
    int flag = 1;
    while(left < right) {
        if(a[left++] == a[right--]) continue;
        else flag = 0; //出现不相等
    }
    return flag;
}
int main()
{
    cin.get(a, 666);
    int right = strlen(a) - 1;
    if(repeat(0, right)) 
        cout<<"YES"<<endl;
    else
        cout<<"NO"<<endl;
    return 0;
}
bananab
YES

bababb
NO

栈还可验证括号匹配,end~

第三节 纸牌游戏--小猫钓鱼

本题结合了栈和队列

我们需要两个队列一个栈来模拟整个游戏

题目

寒假到了,小哼和小哈出来约会,他们来到了一家咖啡馆玩桌游。

这款桌游是一个叫“小猫钓鱼”的扑克游戏:

1,

将一份扑克牌平均分两份,每人一份。小哼先拿出了自己手中的第一张扑克牌放到桌上,然后小哈也拿出自己受手中的第一张扑克牌,并放在小哼刚打出的扑克牌上面,就像这样两人交替出牌

2,

出牌时,如果某人打出的牌与桌上某张牌牌面相同,即可将两张相同的牌及中间所夹的牌全部取走,并依次放到自己手中牌的末尾

3,

当任意一人手中的牌全部出完,游戏结束,对手获胜,

假设游戏开始,小哼有6张牌,小哈也有6张,且牌的牌面只有1~9

思路 + 代码

(以下思路中,存在游戏永远无法结束的情况,只是简陋版本)

容易得知,胜负从拿到牌开始就确定了,且桌上每种牌面最多一张

创建一个结构体来实现队列

struct queue
{
    int data[1000];
    int head;
    int tail;
};

 head存储队首,tail存储队尾,数组data存储队列元素,当然大小的设置可以不止1000,数组不越界就好

创建一个结构体来实现栈

struct stack
{
    int data[10];
    int top;
};

 top存储栈顶,数组data存储栈元素,大小设为10,因为只有9种不同牌面,考虑到打出的牌与桌面相同则可取走,桌上最多只能有9张牌,所以10够用了

定义队列变量q1, q2,q1模拟小哼的牌,q2模拟小哈的牌,定义栈变量s模拟桌上的牌

struct queue q1, q2;
struct stack s;

 初始化队列和栈

//初始化q1, q2为空,此时小哼小哈手中无牌
q1.head = 1; q1.tail = 1;
q2.head = 1; q2.tail = 1;
//初始化栈s为空,最开始桌上无牌
s.top = 0;

 分别读入小哼,小哈的牌,每次读入6个数,分别插入q1, q2

for(int i = 1; i <= 6; ++i) {
    cin>>q1.data[q1.tail]; //读入一个数到队尾
    q1.tail++; //队尾往后挪一位
}
for(int i = 1; i <= 6; ++i) {
    cin>>q2.data[q2.tail]; 
    q2.tail++;
}

 小哼出牌

t = q1.data[q1.head]; //小哼亮出一张牌

 小哼打出第一张牌,即q1队首,将这张牌存放在临时变量t种,接下来判断桌上有无与t相同的牌

用一个数组记录桌上的牌,由于牌面只有1~9,开一个大小为10的数组即可

int book[10];

 将book[1]~book[9]初始化为0,因为开始桌上无牌

for(int i = 0; i <= 9; ++i)
    book[i] = 0;

 如果桌上多了一张牌面2的牌,则book[2] = 1,因为牌面为2的牌桌上有了。若牌面2的牌被拿走,就将book[2]设置为0,表示桌上没有牌面2的牌了,此时只需一个if来判断桌上是否有某张牌(类似桶排序)

t = q1.data[q1.head]; //小哼先出牌
if(book[t] == 0) { //桌面没有牌面为t的牌,小哼本轮没有赢牌
    q1.head++; //已打出的牌出队
    s.top++; //注意top初始为0
    s.data[s.top] = t; //已打出的牌入栈,即放到桌面
    book[t] = 1; //标记桌上有牌面t的牌
}

如果不满足book[t] == 0,小哼可以赢得桌上的牌,将赢得的牌依次放入小哼手中 

else {
    q1.head++; //小哼已出牌,所以要把这张牌出队
    q1.data[q1.tail] = t; //可赢牌,就把刚打出的牌放回手中牌的末尾
    q1.tail++;
    while(s.data[s.top] != t) { //桌上可赢的,依次放到手中末尾
        q1.data[q1.tail] = s.data[s.top]; //依次放入队尾
        q1.tail++;
        s.top--; //栈种少了一张牌,栈顶减1
    }
}

 以上,就是小哼出牌阶段,小哈类似,只是q1变q2

在模拟两人出牌外,加一个while循环判断是否有人出完牌

while(q1.head < q1.tail && q2.head < q2.tail) 
//队列q1, q1都不为空,双方都有牌,继续循环

 最后,输出winner,以及获胜者手中的牌 和 桌上的牌

若小哼获胜,小哈手中无牌,此时q2.head == q2.tail

if(q2.head == q2.tail) {
    cout<<"小哼win"<<endl;
    cout<<"小哼当前手中的牌是";
    for(i = q1.head; i <= q1.tail - 1; ++i)
        cout<<q1.data[i];
    cout<<endl;
    if(s.top > 0) {//若桌上有牌,依次输出
        cout<<"桌上的牌是";
        for(i = 1; i <= s.top; ++i)
            cout<<s.data[i];
        cout<<endl;
    }
    else
        cout<<"桌上已经没有牌了"<<endl;
}

 完整代码

#include<iostream>
using namespace std;
struct queue
{
    int data[1000]; //队列的主体,表示玩家的牌
    int head; //队首
    int tail; //队尾
};
struct stack
{
    int data[10]; //栈的主体,表示桌上的牌
    int top; //栈顶
};
int main()
{
    struct queue q1, q2; //队列变量
    struct stack s; //栈变量
    int book[10];
    int i, t;

    //初始化队列
    q1.head = 1; q1.tail = 1;
    q2.head = 1; q2.tail = 1;
    //初始化栈
    s.top = 0;
    //初始化标记桌上牌的数组
    for(i = 1; i <= 9; ++i)
        book[i] = 0;

    //依次向队列插入小哼的6张牌
    for(i = 1; i <= 6; ++i) {
        cin>>q1.data[q1.tail];
        q1.tail++;
    }
    //依次向队列插入小哈的6张牌
    for(i = 1; i <= 6; ++i) {
        cin>>q2.data[q2.tail];
        q2.tail++;
    }
    while(q1.head < q1.tail && q2.head < q2.tail) { //队列不为空
        t = q1.data[q1.head]; //小哼出的第一张
        //判断当前能否赢
        if(book[t] == 0) {//桌上没有这个牌面的牌
            q1.head++; //打出的牌出队
            s.top++;
            s.data[s.top] = t; //打出的牌入栈
            book[t] = 1; //标记桌上有了牌面t的牌
        }
        else { //桌上有这个牌
            q1.head++; //已打出的出队
            q1.data[q1.tail] = t; //打出的牌放到末尾
            q1.tail++;
            while(s.data[s.top] != t) {//可赢的牌
                book[s.data[s.top]] = 0; //取消标记
                q1.data[q1.tail] = s.data[s.top]; //依次放入队尾
                q1.tail++;
                s.top--; //桌上少了一张,栈顶减1
            }
            //遇到牌面与打出一样的,跳出上面的while
            book[s.data[s.top]] = 0;
            q1.data[q1.tail] = s.data[s.top];
            q1.tail++;
            s.top--;
        }
        if(q1.head == q1.tail) break; //牌出完,游戏结束

        t = q2.data[q2.head]; //小哈出的第一张
        //判断当前能否赢
        if(book[t] == 0) {//桌上没有这个牌面的牌
            q2.head++; //打出的牌出队
            s.top++;
            s.data[s.top] = t; //打出的牌入栈
            book[t] = 1; //标记桌上有了牌面t的牌
        }
        else { //桌上有这个牌
            q2.head++; //已打出的出队
            q2.data[q2.tail] = t; //打出的牌放到末尾
            q2.tail++;
            while(s.data[s.top] != t) {//可赢的牌
                book[s.data[s.top]] = 0; //取消标记
                q2.data[q2.tail] = s.data[s.top]; //依次放入队尾
                q2.tail++;
                s.top--;
            }
            //遇到牌面与打出一样的,跳出上面的while
            book[s.data[s.top]] = 0;
            q2.data[q2.tail] = s.data[s.top];
            q2.tail++;
            s.top--;
        }
    }

        if(q2.head == q2.tail) {
            cout<<"小哼win"<<endl;
            cout<<"小哼当前手中的牌是 ";
            for(i = q1.head; i <= q1.tail - 1; ++i)
                cout<<q1.data[i]<<" ";
            cout<<endl;
            if(s.top > 0) { //桌上有牌就输出
                cout<<"桌上的牌是 ";
                for(i = 1; i <= s.top; ++i)
                    cout<<s.data[i]<<" ";
            }
            else
                cout<<"桌上已经没有牌了"<<endl;
        }
        else {
            cout<<"小哈win"<<endl;
            cout<<"小哈当前手中的牌是 ";
            for(i = q2.head; i <= q2.tail - 1; ++i)
                cout<<q2.data[i]<<" ";
            cout<<endl;
            if(s.top > 0) { //桌上有牌就输出
                cout<<"桌上的牌是 ";
                for(i = 1; i <= s.top; ++i)
                    cout<<s.data[i]<<" ";
            }
            else
                cout<<"桌上已经没有牌了"<<endl;
        }
    return 0;
}

输入输出

2 4 1 2 5 6
3 1 3 5 6 4
小哈win
小哈当前手中的牌是 1 6 5 2 3 4 1
桌上的牌是 3 4 5 6 2

1 6 2 8 5 5
3 4 6 7 3 2
小哈win
小哈当前手中的牌是 2 6 2 4 6 3 5 7 8 3
桌上的牌是 1 5

7 8 9 1 2 3
6 4 2 3 3 8
小哼win
小哼当前手中的牌是 3 3 2 4 1 9 3 8 2
桌上的牌是 7 6 8

 跟着书本敲完一遍,好像学到了什么,又好像什么都没学到  🙄(只能说有了印象,大概理解)

由于是简陋版本,只能通过部分测试数据,这个程序显然是不够“健壮”的

第四节 链表 

只通过啊哈算法里讲的那一点点,是不够的,所以我通过网上的资源加深理解

要善用网上资源,网络不是只让你打游戏,撩妹😂,娱乐的

 1,blibli(1.5倍速) (40分钟)

1个小时学会单链表,C语言数据结构专题_哔哩哔哩_bilibili

2,blibli(1.5倍速) (15分钟)

2.4链表_哔哩哔哩_bilibili

3,CSDN(30分钟) 链表基础知识详解(非常详细简单易懂)_不秃也很强的博客-CSDN博客_链表

然后我开始结合自己的理解,照着书本敲关键知识点(90分钟)

基础概念

1,

链表由一个个节点构成,每个节点采用结构体的形式组织,它是一种物理存储上非连续,通过指针连接的线性存储结构

2,

链表与数组的区别:

数组通过开辟连续的内存来存储数据,有起始和结束地址;

而链表通过对节点的插入和删除实现对数据的读取,没有头尾之分

使用链表可以更灵活地对数据进行插入和删除

3,

链表由结构体变量(节点)相连构成,每一个节点由数据域指针域组成,数据域存放具体数值,指针域存放下一节点首地址

4,

结构体中的"."和"->"可以读作“的”,比如p->next读作p的next,p->data读作p的data,用来访问结构体成员,区别是,指针变量用"->",一般变量用"."访问成员

实现链表

如何实现链表呢?使用指针和动态分配内存函数malloc来实现

指针: 

 指针中的 * 表示取内容或者说解引用,& 表示取地址,比如

int a; cin>>a; int *p; p = &a; cout<<*p;

&将a变量的地址赋值给指针p,指针p表示a变量的地址

*使p取得a所指向地址的值,*p表示a变量的地址所指向的值

malloc函数:(头文件#include<cstdlib>)

1,malloc(sizeof(int))

等价于 malloc(4); 从内存中申请分配4个字节大小的内存空间

2,

现在已经成功从内存中申请了4个字节的空间来存放一个整数,如何对这个空间操作呢?

这就需要一个指针来指向这个空间,即存储这个空间的首地址

3,(int *)

malloc()函数返回类型使void *类型,表示未确定类型的指针,该类型可强制转换为任何其他类型指针,此处我们需要强转整型,所以是(int *)

int *p; //定义指针p
p = (int *)malloc(sizeof(int));

malloc()函数动态申请空间:

#include<iostream>
#include<cstdlib> //malloc()
using namespace std;
int main()
{
    int *p; //定义指针p
    //指针p获取动态分配的内存地址
    p = (int *)malloc(sizeof(int));
    *p = 10; //向p所指向的内存存入10
    cout<<*p<<endl; //输出p指向内存中的值
    return 0;
}
10

 每一个节点由两部分组成,左边部分存放具体数值,右边部分存储下一个节点的地址(称为后继指针),这里我们定义一个结构体类型来存储这个节点

struct node //节点node
{
    int data; //数据域
    struct node *next; //指针域,存放下一节点首地址
};

 

 上面代码中,我们定义了一个叫node的结构体类型,它有俩成员,第一个成员叫data,用来存储具体的数值;第二个成员是一个指针,用来存储下一节点的地址。因为下一节点的类型也是struct node,所以这个指针的类型也必须是struct node *类型的指针。

如何建立链表呢?首先需要一个头指针head指向链表的最开始,链表未建立时头指针为空(可理解为指向空节点) 

struct node *head;
head = NULL; //头指针初始为空

 现在创建第一个节点,并用临时指针p指向这个节点

struct node *p;
p = (struct node *)malloc(sizeof(struct node));
//使指针p变成了结构体变量

 设置第一个节点的左半部分和右半部分

int a;
cin>>a;
p->data = a; //将a的值存储到节点的数据域中
p->next = NULL; //后继指针为空,即下一节点为空

 

 可以这样念“pdata等于a,pnext为空”,->是结构体指针运算符,用来访问内部成员

设置头指针:(方便从头遍历整个链表) 

1,如果这是第一个创建的节点,头指针指向这个节点

2,如果不是,上一节点的后继指针指向这个节点

 

if(head == NULL)
    head = p; //第一个节点被头指针指向
else
    q->next = p; //否则上一节点后继指针指向这里

最后将指针p也指向当前节点,因为下一步临时指针p会指向新节点 

q = p; //指针q指向当前节点

 完整代码1

#include<iostream>
#include<cstdlib> //malloc()
using namespace std;
struct node
{
    int data;
    struct node *next;
};
int main()
{
    struct node *head, *p, *q, *t;
    int i, n, a;
    cin>>n;
    head = NULL; //头指针初始为空
    for(i = 1; i <= n; ++i) {
        cin>>a;
        //动态申请一个空间存放节点,临时指针p指向这个节点
        p = (struct node *)malloc(sizeof(struct node));
        p->data = a; //数据存储到数据域
        p->next = NULL; //后继指针指向空
        if(head == NULL)
            head = p; //第一个节点让头指针指向
        else
            q->next = p; //否则上一节点后继指针指向当前节点
        q = p; //上一节点指针指向当前节点
    }

    //输出链表中所有数
    t = head;
    while(t != NULL) {
        cout<<t->data<<" ";
        t = t->next; //继续下一节点
    }
    return 0;
}
9
2 3 5 8 9 10 18 26 32
2 3 5 8 9 10 18 26 32

注意,上述代码没有free(释放动态申请的空间),很不安全 

插入数据

接下来,往链表插入6

 

关于插入,这里有个关键: 

下方完整代码第38,39行,初始指针t指向5,p指向6,t的next指向8

  1. p->next = t->next;

  2. t->next = p;

这两步相当于链表断开,接上了一个新的元素

p的next指向原来的t的next(指向了8的地址)

t的next再指向p的地址,顺序不能调换

插入后,t的next指向p的地址,p的next指向8的地址

 首先用临时指针t从链表头部开始遍历

t = head; //从链表头部开始遍历

 等指针t下一节点的值比6大,将6插到两节点中间,即t->next->data大于6时插入

t->next->data跟我念t的next的data

cin>>a; //读入待插入的数
while(t != NULL) {//未到达链表尾部
    if(t->next == NULL || 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; //继续下一节点
}

 完整代码2

#include<iostream>
#include<cstdlib> //malloc()
using namespace std;

//创建一个结构体表示节点
struct node
{
    int data;
    struct node *next;
};

int main()
{
    struct node *head, *p, *q, *t; //p,q,t都是临时指针
    int i, n, a;
    cin>>n; //n个数
    head = NULL; //头指针初始为空
    for(i = 1; i <= n; ++i) {
        cin>>a;
        //动态申请空间存放一个节点,临时指针p指向该节点
        p = (struct node *)malloc(sizeof(struct node));
        p->data = a;
        p->next = NULL; //当前节点下一节点为空
        if(head == NULL)
            head = p; //若为第一个创建的,头指针指向该节点
        else
            q->next = p; //上一节点后继指针指向当前节点
        q = p; //指针q指向当前节点
    }

    cin>>a; //待插入的数
    t = head; //从链表头部开始遍历
    while(t != NULL) {
        if(t->next == NULL || 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) {
        cout<<t->data<<" ";
        t = t->next; //继续下一节点
    }
    return 0;
}
9
2 3 5 8 9 10 18 26 32
6
2 3 5 6 8 9 10 18 26 32

耗时3.5小时终于完成了第四节的学习

第五节 模拟链表

模拟链表不需要指针,却能起到链表的作用,模拟链表还能实现双向链表循环链表

学习模拟链表可以加深我们对链表的理解,因为它们的思想是一样的,只是工具不同

它使用一个数组存放序列中的数字,使用另一个数组存放下一数字的下标

 

 比如上面两个数组,

right[1] == 2表示数组data第1个元素右边元素的下标为2

right[9] == 0表示数组data第9个元素右边元素不存在

 现在需要将6插入5和8中间,怎么做呢?只需要

data[10] = 6; //加在最后

right[3] = 10; //3号右边的元素存放在data[10]

right[10] = 4; //10号元素右边的元素存放在data[4]

 

完整代码 

#include<iostream>
using namespace std;
int main()
{
    int data[110], right[110];
    int n;
    cin>>n;
    for(int i = 1; i <= n; ++i)
        cin>>data[i]; //读入数据
    for(int i = 1; i <= n; ++i) { //初始化数组right
        if(i != n) right[i] = i + 1;
        else right[i] = 0;
    }
    cin>>data[n + 1]; //待插入数

    //从链表头部开始遍历
    int t = 1;
    while(t != 0) {
        if(data[right[t]] > data[n + 1]) {
            right[n + 1] = right[t]; //right[10] = right[3]
            right[t] = n + 1; //right[3] = 10
            break; //插入完跳出循环
        }
        t = right[t]; //t自增至满足条件或遍历完
    }
    //输出链表所有数
    t = 1; //类似链表中 指针t = head;
    //t = 1少了会从中间开始输出
    while(t != 0) {
        cout<<data[t]<<" ";
        t = right[t];
    }
    return 0;
}
9
2 3 5 8 9 10 18 26 32
6
2 3 5 6 8 9 10 18 26 32

代码中 data[n + 1] == 6 ,当满足插入条件时,t == 3, right[n + 1] = 4, right[t] == 10

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

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

相关文章

CACHE 概念

CACHE 概念 CPU 读写指令或者数据&#xff0c;可能直接从寄存器查取&#xff0c;也可能经过 TLB &#xff0c;经过 MMU&#xff0c;经过高速缓存&#xff0c;经过内存&#xff0c;经过外部存储器。这里面有一个 Cache 的概念&#xff0c;想多了解下了&#xff0c;于是有这这一…

【自定义类型】带你走进结构体、枚举、联合

欢迎来到小王学代码的博客 在字符型函数之后&#xff0c;我们接下来要学习的是自定义类型中的结构体、枚举、联合 目录 前言 一、结构体 1.1结构体的声明 1.2 特殊声明和结构自引用 1.4结构体的自引用 1.5结构体变量的定义和初始化 1.6 结构体内存对齐 1.7修改默认对齐…

【杂烩】Latex中的一些技巧备忘录

1. subfigure 和 minipage 环境的运用 首先是多张图组合到一起&#xff0c;左侧和下方备注列标题和行标题。 使用的时候需要的包&#xff1a; minipage不需要 \usepackage{subfigure} \usepackage{graphicx}代码1&#xff1a; \begin{figure*}[htbp]\centering%第一行图片展…

微信小程序怎么实现拍照功能,以及授权,拍完照保存到本地。

写微信小程序就是要不停的翻阅官方文档查阅所需要的需求。API的使用说明wx.getSetting 获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限 scope.camera相机wx.authorize 提前向用户发起授权请求。调用后会立刻弹窗询问用户是否同意授权小程序使用某项功能或获…

复盘!!指针 ,地址 ,数组之间的联系

文章目录前言一、直接看题二、直接上代码总结前言 工作摸鱼 闲来无事 一、直接看题 二、直接上代码 所以&#xff0c;这个题目答案是5. 我来盘给你&#xff1a;int a[5] {1,2,3,4,5}; 1数组名本身就是表示数组首元素地址 2对数组名取地址&#xff1a;表示的是整个数组的地址…

Android抓包Charles入门教程

Android抓包工具有很多&#xff0c;有轻量的如httpCanary直接安装在手机上通过代理进行抓包&#xff0c;有功能强大的WireShark PC 端的抓包工具&#xff08;也要设置代理啦&#xff09;,还有老牌抓包工具Finder和Charles以及mitmproxy 。 手机上的抓包工具远离基本差不多&…

MAC(m1)-CentOS8 Docker设置MySQL数据持久化

Docker安装部署Mysql8的过程(以作数据持久化)_docker_脚本之家 Docker安装Mysql并映射_小小小果子的博客-CSDN博客_docker mysql 映射 Docker中安装mysql并把配置等挂载在外面(开启多个mysql)_遗忘de神话的博客-CSDN博客_dock中安装了mysql,还能在外部再安装一个mysql吗 mys…

Windows实时运动控制软核(五):LOCAL高速接口测试之VC6.0

今天&#xff0c;正运动小助手给大家分享一下MotionRT7的安装和使用&#xff0c;以及使用VC6.0对MotionRT7开发的前期准备。 01 MotionRT7简介 MotionRT7是深圳市正运动技术推出的跨平台运动控制实时内核&#xff0c;也是国内首家完全自主自研&#xff0c; 自主可控的Windows…

电脑怎么录制屏幕视频,3种方法,轻松弄懂

在日常生活当中&#xff0c;电脑录屏的使用场景也变得越来越多了起来&#xff0c;网课录制&#xff0c;微课录制&#xff0c;直播录制&#xff0c;会议录制等等。那么问题来了&#xff0c;电脑怎么录制屏幕视频呢&#xff1f;今天小编就向大家分享3个电脑录制屏幕视频的方法&am…

osg fbo(一),生成颜色缓冲区图片

由于工作需要&#xff0c;重新捡了下shader。很明显&#xff0c;fbo是重中之重。好记性不如烂笔头&#xff0c;先记录下 1&#xff0c;生成一个颜色纹理(为了省事&#xff0c;可以将纹理宽高屏幕宽高) osg::ref_ptr<osg::Texture2D> tex createFloatRectangleTexture(…

Linux安装Docker详细教程

文章目录Docker架构环境说明安装步骤阿里云镜像加速Docker底层原理Docker架构 镜像&#xff08;image&#xff09;: Docker 镜像&#xff08;Image&#xff09;就是一个只读的模板。镜像可以用来创建 Docker 容器&#xff0c;一个镜像可以创建很多容器。 就好似 Java 中的 类和…

Day857.高性能限流器Guava RateLimiter -Java 并发编程实战

高性能限流器Guava RateLimiter Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于高性能限流器Guava RateLimiter的内容。 Guava RateLimiter 是如何解决高并发场景下的限流问题的。 Guava 是 Google 开源的 Java 类库&#xff0c;提供了一个工具类 RateLimiter。先…

2022智能制造世界与中国十大科技进展内容小结

2022智能制造世界与中国十大科技进展内容小结1. 全断面隧道掘进装备行业工业互联网平台2. 大型复杂构件机器人原位高效高质量铣削加工技术及装备3. 西门子SNC—原生数字化工厂4. 亚马逊 数字孪生服务IoT TwinMaker5. Nvidia 新一代智能移动机器人仿真平台6. 北京奔驰的基于大数…

Excel特殊符号及用法

一、公式中常用符号 &#xff1a; 表示一个单元格区域&#xff0c;如A1:B10 - * / 加减乘除运算符> 大于号> 大于等于号< 小于号< 小于等于号<> 不等于号&#xff0c;如IF(A1<>"销售部",,)^数字 乘方运算&#xff0c;如…

第二章Mybatis进阶操作学习

文章目录特殊SQL的执行模糊查询批量操作动态设置表名添加功能获取自增的主键自定义映射resultMap处理字段和属性的映射关系在SQL语句中使用别名使用核心配置文件中的驼峰对应方法使用resultMap自定义映射处理一对一映射处理级联方式处理association标签分步查询分步查询的优点&…

【网安神器篇】——Sqlmap详解

作者名&#xff1a;白昼安全主页面链接&#xff1a;主页传送门创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座右铭&a…

C语言 自定义类型 之 【联合体(共用体)】

文章目录前言联合体(UNION)类型的定义联合体的特点联合体大小的计算联合体使用的典型例题 — 判断机器的大小端写在最后前言 联合体&#xff08;union&#xff09;在C语言中是不常用的&#xff0c;不过存在就必然有其有用之处&#xff0c;这里不深入探讨联合体用在何处有什么用…

R语言实现牛顿插值

文章目录4 差商与牛顿插值差商牛顿插值4 差商与牛顿插值 差商 如果采取间隔不等的采样&#xff0c;差商会变得稍显复杂&#xff0c;对于x0,x1,…,xnx_0,x_1,\ldots,x_nx0​,x1​,…,xn​&#xff0c;若与y0,y1,…,yny_0,y_1,\ldots,y_ny0​,y1​,…,yn​通过映射fff一一对应&…

ASEMI整流桥KBP310电路设计注意事项

编辑-Z 型号&#xff1a;KBP310 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;1000V 最大平均正向整流输出电流&#xff08;IF&#xff09;&#xff1a;3.0A 峰值正向浪涌电流&#xff08;IFSM&#xff09;&#xff1a;60A 每个元件的典型热阻&#xff0…

MySQL LOAD VS DM8 dmfldr

MySQL LOAD VS DM8 dmfldr 背景 某业务系统从MySQL迁移至达梦后&#xff0c;有导入业务文件的功能使用MySQL的LOAD方式将csv文件导入到指定的表中。迁移到达梦后&#xff0c;该功能需要进行对应的调整&#xff08;因为达梦没有LOAD功能&#xff09;&#xff0c;但达梦提供了dm…