《啊哈!算法》总结与概况(二)
第二章
队列
队列是一种特殊的线性结构,它只允许在队列的首部head进行删除,以及在尾部tail进行插入
这两种操作分别被称为“出队”和“入队”。而当队列中没有元素即head=tail时,称其为“空队列”
我们现在可以将队列的三个基本元素(一个数组,两个变量)封装为一个结构体类型
1 | struct queue{ |
上面定义了以一个结构体类型,我们通常把它放在main外面,其定义末尾有一个;
其中,strcut是结构体的关键字,queue是我们为这个结构体起的名字
这个结构体有三个成员:整型数组data,整型head,tail。这样以来,我们就可以把它们当作一个整体来看待
访问结构体内的内部成员使用点(.)
下面这段代码就是我们用结构体实现队列操作的实例
##代码示例
1 | struct queue{ |
栈
栈是一种后进先出的数据结构,它就是栈
栈限定了它只能在一端进行插入和删除的操作,这决定了它“后进先出”的性质
栈的实现也比较简单:利用一个一维数组和一个指向栈顶部的变量(称它为top)即可,我们前面所讲的“插入”和“删除”的操作,就是通过这个”top”来实现的
入栈
入栈的操作很简易
1 | top++ ; |
简化一下,就可以化成一行
1 | s[++top] = a[i]; |
链表(数组模拟)
链表也可以使用数组来实现,操作和基础知识比指针简单
但是个人觉得就思路和操作的清晰,以及对链表的理解而言,还是用指针好
模拟链表介绍
我们可以利用两个数组,分别记录链表要的两个东西:数据和地址
我们使用一个数组data,来存储每个结点的数据
使用另外一个数组right,来存储序列中每一个数右边的数的位置
比如现在二者如下:
位置 1 2 3 4 5 06 07 08 09
data: 2 3 5 8 9 10 18 26 32
right: 2 3 4 5 6 7 8 9 0
right[9] = 0 ,就代表data第九位的的右边没有数据
这里说一下,众所周知数组的第一位下标是0,但是书里按1开始算了
本着以书为本的原则,就没有修改,况且对于做链表而言也没区别
就是习惯了0开始数,看着难受
再如right[1]的值是2,指的是1号元素的右边的元素是2号元素(存放在data[2]中)
下面尝试按大小顺序,把6这个数值加入进去,那么该怎么做呢?
对于data数组比较简单,我们只需要做添加数据这个操作
让我们把这个数据添加到10号位置
位置 1 2 3 4 5 06 07 08 09 10
data: 2 3 5 8 9 10 18 26 32 6
对于right数组,会复杂一点,毕竟要排序和改地址
我们通过观察data数组,我们的6需要添加到5和8之间,也就是3号元素和4号元素之间
5对应的号数是3,8则是4.原本的地址是3号->4号,现在我们要加入6
那么我们新的次序是3号->10号->4号,即:
1.把3号“右边一位”改成10号
2.把10号“右边一位”改成4号
3.4号“右边一位”不变
位置 1 2 3 4 5 06 07 08 09 10
data: 2 3 5 8 9 10 18 26 32 6
right: 2 3 10 5 6 7 08 09 0 4
代码实例:
1 |
|
如上所示,我们得到了一个链表。但是只得到链表是不够的,我们还需要加减数据
链表数据的加减
这里以加数据为例子,删数据同理。方法如下:
1.创建一个临时指针,并且从链表头部一直往下遍历
2.按照条件筛选位置,成功后,将对应的指针修改。让这个被添加的数据处于原本前后两个地址的中间
增加数据的代码实现如下:
1 | //下面来读数据 |
含插入的完整代码
#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=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 ; //t是临时指针,从链表的头部开始遍历
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){
printf("%d",t->data);
t = t->next ; //继续下一个结点
}
getchar();getchar();
return 0 ;
}
小结
个人认为利用指针,能更好地理解链表的思想
虽然学习和消化的时间比较长,但是是值得的
下一节,还有不用指针的模拟链表,但是个人认为少了那种精髓