啊哈!算法-第2章-栈、队列、链表
第1节 解密qq号——队列
- 新学期开始了,小哈是小哼的新同桌(小哈是个大帅哥哦~),小哼向小哈询问 QQ 号, 小哈当然不会直接告诉小哼啦,原因嘛你懂的。所以小哈给了小哼一串加密过的数字,同时 小哈也告诉了小哼解密规则。规则是这样的:首先将第 1 个数删除,紧接着将第 2 个数放到 这串数的末尾,再将第 3 个数删除并将第 4 个数放到这串数的末尾,再将第 5 个数删除… 直到剩下最后一个数,将最后一个数也删除。按照刚才删除的顺序,把这些删除的数连在一 起就是小哈的 QQ 啦。现在你来帮帮小哼吧。小哈给小哼加密过的一串数是“5 3 0 3 9 3 0 1”。
密文 | 删除的数字 | 移到最后的数字 | QQ号 |
---|---|---|---|
53 039301 | 5 | 3 | 5 |
03 93013 | 0 | 3 | 50 |
93 0133 | 9 | 3 | 509 |
01 333 | 0 | 1 | 5090 |
33 31 | 3 | 3 | 50903 |
31 3 | 3 | 1 | 509033 |
3 1 | 3 | 5090333 | |
1 | 1 | 50903331 |
解密的第一步是将第一个数删除,你可以想一下如何在数组中删除一个数呢。最简单的 方法是将所有后面的数都往前面挪动一位,将前面的数覆盖。就好比我们在排队买票,最前 面的人买好离开了,后面所有的人就需要全部向前面走一步,补上之前的空位,但是这样的 做法很耗费时间。
在这里,我将引入两个整型变量 head 和 tail。head 用来记录队列的队首(即第一位), tail 用来记录队列的队尾(即最后一位)的下一个位置。你可能会问:为什么 tail 不直接记 录队尾,却要记录队尾的下一个位置呢?这是因为当队列中只剩下一个元素时,队首和队尾重合会带来一些麻烦。我们这里规定队首和队尾重合时,队列为空。
现在有 n 个数,n 个数全部放入队列之后 head = 0;tail = strlen(QQ);此时 head 和 tail 之间的数就是
目前队列中“有效”的数。如果要删除一个数的话,就将 head++就 OK 了,这样仍然可以保 持 head 和 tail 之间的数为目前队列中“有效”的数。这样做虽然浪费了一个空间,却节省了 大量的时间,这是非常划算的。新增加一个数也很简单,把需要增加的数放到队尾即 q[tail] 之后再 tail++就 OK 啦。
- 在队首删除一个数的操作是 head++;
- 在队尾增加一个数(假设这个数是 x)的操作是 q[tail] = x; tail++;
- 以上操作重复执行若干次,直到head < tail结束即可
#include<iostream>
using namespace std;
const int maxn = 100;
int head, tail;
char QQ[maxn];
int main(){
cin >> QQ;
head = 0; // head指向密文的开始位置,tail指向密文的结束位置+1
tail = strlen(QQ);
// 当队列不为空的时候执行循环
while (head < tail){
// 打印队首,并将队首出队
cout << QQ[head++];
//先将新队首的数添加到队尾,再将队首出队
QQ[tail++] = QQ[head++];
}
return 0;
}
队列是一种特 殊的线性结构,它只允许在队列的首部(head)进行删除操作,这称为“出队”,而在队列 的尾部(tail)进行插入操作,这称为“入队”。
当队列中没有元素时(即 head==tail),称为 空队列。
“先进先出”(First In First Out,FIFO)原则。
struct queue{
int data[100]; // 队列的主体,用来存储内容
int head; // 队首
int tail; // 队尾
};
上面定义了一个结构体类型,我们通常将其放在 main 函数的外面,请注意结构体的定 义末尾有个;号。struct 是结构体的关键字,queue 是我们为这个结构体起的名字。这个结构 体有三个成员分别是:整型数组 data、整型 head 和整型 tail。这样我们就可以把这三个部分 放在一起作为一个整体来对待。你可以这么理解:我们定义了一个新的数据类型,这个新类 型非常强大,用这个新类型定义出的每一个变量可以同时存储一个整型数组和两个整数。
有了新的结构体类型,如何定义结构体变量呢?很简单,这与我们之前定义变量的方式 是一样的,具体做法如下。
struct queue QQ;
请注意 struct queue 需要整体使用,不能直接写 queue q;。这样我们就定义了一个结构体 变量 q。这个结构体变量就可以满足队列的所有操作了。那又该如何访问结构体变量的内部 成员呢?可以使用.号,它叫做成员运算符或者点号运算符,如下:
QQ.head = 1;
QQ.tail = 1;
scanf("%d", &QQ.data[q.tail]);
下面这段代码就是使用结构体来实现的队列操作。
#include<iostream>
using namespace std;
struct queue{
string data; // 队列主体,用来存储内容
int head, tail; // 队首和队尾
};
int main(){
queue QQ;
cin >> QQ.data;
// 初始化队列
QQ.head = 0;
QQ.tail = QQ.data.length();
// //当队列不为空的时候执行循环
while (QQ.head <= QQ.tail){
cout << QQ.data[QQ.head]; //打印队首
QQ.head++; // 将队首出队
QQ.data[QQ.tail] = QQ.data[QQ.head]; // 先将新队首的数添加到队尾
QQ.head++; // 再将队首出队
QQ.tail++;
}
return 0;
}
第2节 解密回文——栈
栈是后进先出的数据 结构。它限定为只能在一端进行插入和删除操作。比如说有一个小桶,小桶的直 径只能放一个小球,我们现在小桶内依次放入 2、1、3 号小球。假如你现在需要拿出 2 号小球, 那就必须先将 3 号小球拿出,再拿出 1 号小球,最后才能将 2 号小球拿出来。在刚才取小球的 过程中,我们最先放进去的小球最后才能拿出来,最后放进去的小球却可以最先拿出来。
栈究竟有哪些作用呢?我们来看一个例子。“xyzyx”是一个回文字符串,所谓回文字符 串就是指正读反读均相同的字符序列,如“席主席”、“记书记”、“aha”和“ahaha”均是回 文,但“ahah”不是回文。通过栈这个数据结构我们将很容易判断一个字符串是否为回文。
首先我们需要读取这行字符串,并求出这个字符串的长度。
char arr[100];
int len;
cin >> arr;
len = strlen(arr);
如果一个字符串是回文的话,那么它必须是中间对称的,我们需要求中点,即:
mid = len / 2 + 1;
接下来就轮到栈出场了。
我们先将 mid 之前的字符全部入栈。因为这里的栈是用来存储字符的,所以这里用来实 现栈的数组类型是字符数组即 char s[101];,初始化栈很简单,top = 0;就可以了。入栈的操作 是top++; s[top] = x(;假设需要入栈的字符暂存在字符变量x中),其实可以简写为arr[++top] = x;。
现在我们就来将 mid 之前的字符依次全部入栈。
for (int i = 0; i <= mid; i++){
s[++top] = arr[i];
接下来进入判断回文的关键步骤。将当前栈中的字符依次出栈,看看是否能与 mid 之后 的字符一一匹配,如果都能匹配则说明这个字符串是回文字符串,否则这个字符串就不是回 文字符串。
for(i = mid+1; i <= len - 1; i++){
if (a[i] != s[top]){
break;
}
top--;
}
if(top == 0)
printf("YES");
else
printf("NO");
最后如果 top 的值为 0,就说明栈内所有的字符都被一一匹配了,那么这个字符串就是 回文字符串。完整的代码如下。
#include<iostream>
using namespace std;
const int maxn = 200;
int main(){
char arr[maxn], s[maxn];
int len, mid, next, top;
cin >> arr; // 读入一行字符串
len = strlen(arr); // 求字符串的长度
mid = len / 2 - 1; // 求字符串的终点
top = 0; // 栈的初始化
// 将mid前的字符一次入栈
for (int i = 0; i <= mid; i++){
s[++top] = arr[i];
}
// 判断字符串的长度是奇数还是偶数,并找出需要进行字符匹配的起始下标
if (len % 2 == 0){
next = mid + 1;
}else{
next = mid + 2;
}
// 开始匹配
for (int i = next; i < len; i++){
if (arr[i] != s[top]){
break;
}
top--;
}
// 如果top的值为0,则说明栈内所有的字符都被一一匹配了
if (top == 0){
cout << "YES";
}else{
cout << "NO";
}
return 0;
}
第3节 纸牌游戏——小猫钓鱼
星期天小哼和小哈约在一起玩桌游,他们正在玩一个非常古怪的扑克游戏——“小猫钓 鱼”。游戏的规则是这样的:将一副扑克牌平均分成两份,每人拿一份。小哼先拿出手中的 第一张扑克牌放在桌上,然后小哈也拿出手中的第一张扑克牌,并放在小哼刚打出的扑克牌 的上面,就像这样两人交替出牌。出牌时,如果某人打出的牌与桌上某张牌的牌面相同,即可将两张相同的牌及其中间所夹的牌全部取走,并依次放到自己手中牌的末尾。当任意一人 手中的牌全部出完时,游戏结束,对手获胜。
假如游戏开始时,小哼手中有 6 张牌,顺序为 2 4 1 2 5 6,小哈手中也有 6 张牌,顺序 为 3 1 3 5 6 4,最终谁会获胜呢?现在你可以拿出纸牌来试一试。接下来请你写一个程序来 自动判断谁将获胜。这里我们做一个约定,小哼和小哈手中牌的牌面只有 1~9。
我们先来分析一下这个游戏有哪几种操作。小哼有两种操作,分别是出牌和赢牌。这恰 好对应队列的两个操作,出牌就是出队,赢牌就是入队。小哈的操作和小哼是一样的。而桌 子就是一个栈,每打出一张牌放到桌上就相当于入栈。当有人赢牌的时候,依次将牌从桌上 拿走,这就相当于出栈。那如何解决赢牌的问题呢?赢牌的规则是:如果某人打出的牌与桌 上的某张牌相同,即可将两张牌以及中间所夹的牌全部取走。那如何知道桌上已经有哪些牌 了呢?最简单的方法就是枚举桌上的每一张牌,当然也有更好的办法我们待会再说。OK, 小结一下,我们需要两个队列、一个栈来模拟整个游戏。
首先我们先来创建一个结构体用来实现队列,如下。
struct queue{
int data[1000];
int head;
int tail;
}
上面代码中 head 用来存储队头,tail 用来存储队尾。数组 data 用来存储队列中的元素, 数组 data 的大小我预设为 1000,其实应该设置得更大一些,以防数组越界。当然对于本题 的数据来说 1000 已经足够了。
再创建一个结构体用来实现栈,如下。
struct stack{
int data[10];
int top;
};
其中 top 用来存储栈顶,数组 data 用来存储栈中的元素,大小设置为 10。因为只有 9 种不同的牌面,所以桌上最多可能有 9 张牌,因此数组大小设置为 10 就够了。提示一下: 为什么不设置为 9 呢?因为 C ++数组下标是从 0 开始的。
接下来我们需要定义两个队列变量 q1 和 q2。q1 用来模拟小哼手中的牌,q2 用来模拟小 哈手中的牌。定义一个栈变量 s 用来模拟桌上的牌。
struct queue heng, ha;
struct stack table;
接下来来初始化一下队列和栈。
// 初始化队列heng, ha为空,此时两人手中都还没有牌
heng.head = 1;
heng.tail = 1;
ha.head = 1;
ha.tail = 1;
// 初始化栈table为空,最开始的时候桌上也没有牌
table.top = 0;
接下来需要读入小哼和小哈最初时手中的牌,分两次读入,每次读入 6 个数,分别插入 heng 和 ha 中。
void input(queue &my_card){
for (int i = 1; i <= 6; i++){
// 读入一个数到队尾
cin >> my_card.data[my_card.tail++];
}
}
input(heng);
input(ha);
#include <iostream>
using namespace std;
const int maxn = 1000;
// 通过队列来存储两个人手中的牌
struct queue{
int data[maxn];
int head = 1; // 手中无牌
int tail = 1; // 手中无牌
};
struct stack{
// 因为只有编号为1 - 9的牌,所以桌上最多有十张牌
// 因此数组大小设置为10就足够了
int data[15];
int top = 0; //最开始的时候桌上没有牌
};
void input(queue &my_card){
for (int i = 1; i <= 6; i++){
// 读入一个数到队尾
cin >> my_card.data[my_card.tail++];
}
}
int book[15];// 表示桌面上有哪些牌
queue heng, ha;
stack table;
int main(){
input(heng);
input(ha);
// 当队列不为空的时候执行循环
while (heng.head < heng.tail && ha.head < ha.tail){
// 小哼出一张牌
int tmp = heng.data[heng.head];
// 判断小哼当前打出的牌是否能赢牌
if (book[tmp] == 0){ // 表明桌上没有牌面为tmp的牌
heng.head++; //小哼已经打出一张牌,所以要把打出的牌出队
table.top++;
table.data[table.top] = tmp; // 把打出的牌放到桌上,即入栈
book[tmp] = 1; // 标记桌上现在已经有牌面为tmp的牌
}else{ // 表明桌上有牌面为tmp的牌, 小哼本轮可以赢牌
heng.head++; // 小哼已经打出一张牌,所以要把打出的牌出队
heng.data[heng.tail] = tmp; // 将打出的牌放到手中牌的末尾
heng.tail++;
// 把桌上可以赢得的牌依次放到手中牌的末尾
while (table.data[table.top] != tmp){
book[table.data[table.top]] = 0; // 取消标记
heng.data[heng.tail] = table.data[table.top]; // 依次放入队尾
heng.tail++;
table.top--; // 栈中少了一张牌,所以栈顶要减1
}
}
// 小哈出牌
tmp = ha.data[ha.head]; // 小哈出一张牌
// 判断小哈当前打出的牌是否能赢牌
if (book[tmp] == 0){
ha.head++;
table.top++;
table.data[table.top] = tmp;
book[tmp] = 1;
}else{
ha.head++;
ha.data[ha.tail] = tmp;
ha.tail++;
while (table.data[table.top] != tmp){
book[table.data[table.top]] = 0;
ha.data[ha.tail] = table.data[table.top];
ha.tail++;
table.top--;
}
}
}
if (ha.head == ha.tail){
printf("小哼win\n小哼当前手中的牌是:");
for (int i = heng.head; i <= heng.tail - 1; i++)
printf(" %d", heng.data[i]);
}else{
printf("小哈win\n小哈当前手中的牌是:");
for (int i = ha.head; i <= ha.tail - 1; i++)
printf(" %d", ha.data[i]);
}
if (table.top > 0){ // 如果桌上有牌则依次输出桌上的牌
printf("\n桌上的牌是:");
for (int i = 1; i <= table.top; i++){
printf(" %d", table.data[i]);
}
}else{
printf("\n桌上已经没有牌了");
}
cout << endl;
return 0;
}
优化代码
#include <iostream>
using namespace std;
const int maxn = 1000;
// 通过队列来存储两个人手中的牌
struct queue{
int data[maxn];
int head = 1; // 手中无牌
int tail = 1; // 手中无牌
};
struct stack{
// 因为只有编号为1 - 9的牌,所以桌上最多有十张牌
// 因此数组大小设置为10就足够了
int data[15];
int top = 0; //最开始的时候桌上没有牌
};
int book[15];// 表示桌面上有哪些牌
queue heng, ha;
stack table;
void input(queue &my_card){
for (int i = 1; i <= 6; i++){
// 读入一个数到队尾
cin >> my_card.data[my_card.tail++];
}
}
void show_card(queue &per){
int tmp = per.data[per.head]; // 出牌
// 判断小哼当前打出的牌是否能赢牌
if (book[tmp] == 0){ // 表明桌上没有牌面为tmp的牌
per.head++; //小哼已经打出一张牌,所以要把打出的牌出队
table.top++;
table.data[table.top] = tmp; // 把打出的牌放到桌上,即入栈
book[tmp] = 1; // 标记桌上现在已经有牌面为tmp的牌
}else{ // 表明桌上有牌面为tmp的牌, 小哼本轮可以赢牌
per.head++; // 小哼已经打出一张牌,所以要把打出的牌出队
per.data[per.tail] = tmp; // 将打出的牌放到手中牌的末尾
per.tail++;
// 把桌上可以赢得的牌依次放到手中牌的末尾
while (table.data[table.top] != tmp){
book[table.data[table.top]] = 0; // 取消标记
per.data[per.tail] = table.data[table.top]; // 依次放入队尾
per.tail++;
table.top--; // 栈中少了一张牌,所以栈顶要减1
}
}
}
void winer(string name, queue &per){
cout << name << "win\n" << name << "当前手中的牌是:";
for (int i = per.head; i <= per.tail - 1; i++)
printf(" %d", per.data[i]);
}
int main(){
input(heng);
input(ha);
// 当队列不为空的时候执行循环
while (heng.head < heng.tail && ha.head < ha.tail){
show_card(heng); // 小哼出牌
show_card(ha); // 小哈出牌
}
if (ha.head == ha.tail)
winer("小哼", heng);
else
winer("小哈", ha);
if (table.top > 0){ // 如果桌上有牌则依次输出桌上的牌
printf("\n桌上的牌是:");
for (int i = 1; i <= table.top; i++){
printf(" %d", table.data[i]);
}
}else{
printf("\n桌上已经没有牌了");
}
cout << endl;
return 0;
}
第4节 链表
在存储一大波数的时候,我们通常使用的是数组,但有时候数组显得不够灵活,比如下面这个例子。
有一串已经从小到大排好序的数 2 3 5 8 9 10 18 26 32。现需要往这串数中插入 6 使其得到的新序列仍符合从小到大排列。如我们使用数组来实现这一操作,则需要将 8 和 8 后面的数都依次往后挪一位,如下:
这样操作显然很耽误时间,如果使用链表则会快很多。那什么是链表呢?请看下图。
此时如果需要在 8 前面插入一个 6,就只需像下图这样更改一下就可以了,而无需再将8 及后面的数都依次往后挪一位。是不是很节省时间呢?
那么如何实现链表呢?在 C 语言中可以使用指针和动态分配内存函数 malloc 来实现。指针?天啊!如果你在学习 C 语言的时候没有搞懂指针,或者还不知道指针是啥,不要紧,我们现在就回顾一下指针。指针其实超级简单。如果你已经对指针和 malloc 了如指掌则可以跳过下面这一小段,继续往后看。先看下面两行语句。
int a;
int *p;
第一行我们很熟悉了,就是定义一个整型变量 a。第二行你会发现在 p 前面多了一个 * 号,这就表示定义了一个整型指针变量 p。即定义一个指针,只需在变量前面加一个 * 号就OK 啦。
接下来,指针有什么作用呢?答案是:存储一个地址。确切地说是存储一个内存空间的地址,比如说整型变量 a 的地址。严格地说这里的指针 p 也只能存储“一个存放整数的内存空间”的地址,因为在定义的时候我们已经限制了这一点(即定义的时候*p 的前面是 int)。当然你也可以定义一个只能用来存储“一个存放浮点数的内存空间”的地址,例如:
double *p;
简单地说,指针就是用来存储地址的。你可能要问:不就是存储地址嘛,地址不都一样吗,为什么还要分不同类型的指针呢?不要着急,待会后面再解释。接下来需要解决的一个问题:整型指针 p 如何才能存储整型变量 a 的地址呢?很简单,如下:
p = &a;
&这个符号很熟悉吧,就是经常在 scanf 函数中用到的&。&叫取地址符。这样整型指针p 就获得了(存储了)整型变量 a 的地址,我们可以形象地理解整型指针 p 指向了整型变量a。p 指向了 a 之后,有什么用呢?用处就是我们可以用指针 p 来操作变量 a 了。比如我们可以通过操作指针 p 来输出变量 a 的值,如下:
#include <iostream>
using namespace std;
int main(){
int a = 10;
int *p; //定义一个指针p
p = &a; //指针p获取变量a的地址
printf("%d", *p); //输出指针p所指向的内存中的值
return 0;
}
这里 printf 语句里面p 中的号叫做间接运算符,作用是取得指针 p 所指向的内存中的值。在 C 语言中*号有三个用途,分别是:
1.乘号,用做乘法运算,例如 5*6。
2.申明一个指针,在定义指针变量时使用,例如 int *p;。
3.间接运算符,取得指针所指向的内存中的值,例如 printf(“%d”,*p);。
到目前为止,你可能还是觉得指针没啥子实际作用,好好的变量 a 想输出是的话直接printf(“%d”,a); 不完了,没事搞个什么指针啊,多此一举。嗯,到目前为止貌似是这样的O(∩_∩)O 哈哈~~不要着急,真枪实弹地来了。
回想一下,我们想在程序中存储一个整数 10,除了使用 int a;这种方式在内存中申请一块区域来存储,还有另外一种动态存储方法。
malloc(4);
malloc 函数的作用就是从内存中申请分配指定字节大小的内存空间。上面这行代码就申请了 4 个字节。如果你不知道 int 类型是 4 个字节的,还可以使用 sizeof(int)获取 int 类型所占用的字节数,如下:
malloc(sizeof(int));
现在你已经成功地从内存中申请了 4 个字节的空间来准备存放一个整数,可是如何来对这个空间进行操作呢?这里我们就需要用一个指针来指向这个空间,即存储这个空间的首地址。
int *p;
p = (int *)malloc(sizeof(int));
需要注意,malloc 函数的返回类型是 void * 类型。void * 表示未确定类型的指针。在 C和 C++中,void * 类型可以强制转换为任何其他类型的指针。上面代码中我们将其强制转化为整型指针,以便告诉计算机这里的 4 个字节作为一个整体用来存放整数。还记得我们之前遗留了一个问题:指针就是用来存储内存地址的,为什么要分不同类型的指针呢?因为指针变量存储的是一个内存空间的首地址(第一个字节的地址),但是这个空间占用了多少个字节,用来存储什么类型的数,则是由指针的类型来标明的。这样系统才知道应该取多少个连续内存作为一个数据。
OK,现在我们可以通过指针 p 对刚才申请的 4 个字节的空间进行操作了,例如我们向这个空间中存入整数 10,如下:
*p = 10;
完整代码如下,注意当在程序中使用 malloc 函数时需要用到 stdlib.h 头文件。
#include <iostream>
// #include <stdlib.h>
using namespace std;
int main(){
int *p; //定义一个指针p
p = (int *)malloc(sizeof(int)); //指针p获取动态分配的内存空间地址
*p = 10; //向指针p所指向的内存空间中存入10
printf("%d", *p); //输出指针p所指向的内存中的值
return 0;
}
到这里你可能要问:为什么要用这么复杂的办法来存储数据呢?因为之前的方法,我们必须预先准确地知道所需变量的个数,也就是说我们必须定义出所有的变量。比如我们定义了 100 个整型变量,那么程序就只能存储 100 个整数,如果现在的实际情况是需要存储 101个,那必须修改程序才可以。如果有一天你写的软件已经发布或者交付使用,却发现要存储1000 个数才行,那就不得不再次修改程序,重新编译程序,发布一个新版本来代替原来的。而有了 malloc 函数我们便可以在程序运行的过程中根据实际情况来申请空间。
啰嗦了半天,总算介绍完了什么是指针以及如何动态申请空间。注意,本节接下来的代码对于还没有理解指针的朋友来说可能不太容易,不要紧,如果你痛恨指针,大可直接跳过下面的内容直接进入下一节。下一节中我将介绍链表的另外一种实现方式——数组模拟链表。
首先我们来看一下,链表中的每一个结点应该如何存储。
每一个结点都由两个部分组成。左边的部分用来存放具体的数值,那么用一个整型变量就可以;右边的部分需要存储下一个结点的地址,可以用指针来实现(也称为后继指针)。这里我们定义一个结构体类型来存储这个结点,如下。
struct node{
int data;
node *next;
};
上面代码中,我们定义了一个叫做 node 的结构体类型,这个结构体类型有两个成员。第一个成员是整型 data,用来存储具体的数值;第二个成员是一个指针,用来存储下一个结点的地址。因为下一个结点的类型也是 struct node,所以这个指针的类型也必须是 struct node* 类型的指针。
如何建立链表呢?首先我们需要一个头指针 head 指向链表的最开始。当链表还没有建立的时候头指针 head 为空(也可以理解为指向空结点)。
struct node *head;
head = NULL;//头指针初始为空
现在我们来创建第一个结点,并用临时指针 p 指向这个结点。
struct node *p;
//动态申请一个空间,用来存放一个结点,并用临时指针p指向这个结点
p = (struct node *)malloc(sizeof(struct node));
接下来分别设置新创建的这个结点的左半部分和右半部分。
scanf("%d",&a);
p->data = a;//将数据存储到当前结点的data域中
p->next = NULL;//设置当前结点的后继指针指向空,也就是当前结点的下一个结点为空
上面的代码中我们发现了一个很奇怪的符号“->”。->叫做结构体指针运算符,也是用来访问结构体内部成员的。因为此处 p 是一个指针,所以不能使用.号访问内部成员,而要使用->。
下面来设置头指针并设置新创建结点的*next 指向空。头指针的作用是方便以后从头遍历整个链表。
if(head==NULL)
head=p;//如果这是第一个创建的结点,则将头指针指向这个结点
else
q->next=p;//如果不是第一个创建的结点,则将上一个结点的后继指针指向当前结点
如果这是第一个创建的结点,则将头指针指向这个结点。
如果不是第一个创建的结点,则将上一个结点的后继指针指向当前结点。
最后要将指针 q 也指向当前结点,因为待会儿临时指针 p 将会指向新创建的结点。
q = p;//指针q也指向当前结点
完整代码如下。
#include <stdio.h>
#include <stdlib.h>
//这里创建一个结构体用来表示链表的结点类型
using namespace std;
struct node{
int data;
node *next;
};
int main(){
node *head,*p,*q,*t;
int i,n,a;
scanf("%d", &n);
head = NULL;//头指针初始为空
for(i = 1; i <= n; i++){//循环读入n个数
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;
while(t != NULL){
printf("%d ", 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
*/
接下来需要往链表中插入 6,操作如下。
首先用一个临时指针 t 从链表的头部开始遍历。
t = head;//从链表头部开始遍历
等到指针 t 的下一个结点的值比 6 大的时候,将 6 插入到中间。即 t->next->data 大于 6时进行插入,代码如下。
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;//继续下一个结点
}
完整代码如下:
#include <iostream>
#include <stdlib.h>
//这里创建一个结构体用来表示链表的结点类型
using namespace std;
struct node{
int data;
node *next;
};
int main(){
node *head,*p,*q,*t;
int i,n,a;
scanf("%d", &n);
head = NULL;//头指针初始为空
for(i = 1; i <= n; i++){//循环读入n个数
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也指向当前结点
}
scanf("%d", &a);//读入待插入的数
t = head;//从链表头部开始遍历
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;//继续下一个结点
}
return 0;
}
/*
9
2 3 5 8 9 10 18 26 32
6
2 3 5 6 8 9 10 18 26 32
*/