1. 链表的概念
顺序表 → 静态存储分配 → 事先确定容量
链表 → 动态存储分配 → 运行时分配空间
1.1 单链表:线性表的链接存储结构
存储思想:用一组任意的存储单元存放线性表的元素——不连续、零散分布
1.2 单链表存储特点
-
逻辑次序和物理次序不一定相同
-
元素之间的逻辑关系用指针表示
单链表是由若干结点构成的,节点只有一个指针域。
1.3 单链表的结点结构
-
data:数据域,存储数据元素
-
next:指针域,存储指向后继结点的地址
typedef struct node
{
DataType data; // 数据域
struct node *next; // 指针域
}Node,*Link;
Node st; 等价于 struct node st;
Link p; 等价于 struct node *p; (p=&st;)
Node变成了一个类型,可以去声明一个结构体变量;Link也变成了一个类型,用来声明一个指向结构体的指针
1.4 如何申请一个结点?
p = (Link)malloc(sizeof(Node)); 等价于 p = (struct node *)malloc(sizeof(Node));
1.5 如何引用数据元素?
(*p).data / p -> data
1.6 如何引用指针域?
p -> next
1.7 什么是存储结构?
数据元素之间的逻辑关系的表示,将实际存储地址抽象。
头指针:指向第一个结点的地址
尾标志:终端结点的指针域为空
空表:head=NULL
非空表:
如何将空表与非空表统一?——引入头结点
头结点:在单链表的第一个元素结点之前附设一个类型相同的节点
2. 单链表的实现
2.1 单链表的遍历操作
操作接口:void displayNode(Link head);
void displayNode(Link head)
{
p = head->next;
while(p != NULL)
{
printf("%d", p->data);
p = p->next;
}
}
p = p->next语句改成 p++ 能否完成指针后移? 不可以!
链表中结点的存储不是连续的,是零散的!
2.2 求单链表的元素个数
操作接口:int length(Link head);
int length(Link head)
{
p = head->next;
count = 0; //初始化指针p和累加器count
while(p != NULL)
{
p = p->next; //指针p后移
count ++;
}
return count; //注意count的初始化和返回值之间的关系
}
2.3 单链表的查找操作
int queryNode(Link head, DataType x) //也可以改成bool类型
{
p = head->next;
count = 0;
while(p != NULL)
{
if(p->data == x)
{
print(data); //找到则调用输出函数,并提前返回true
return ture;
}
p = p->next;
}
//如果循环结束了,说明没有找到
return false;
}
2.4 单链表的插入操作
操作接口:void insertNode(Link head, int i, DataType x);
bool insertNode(Link head, int i, DataType x)
{
p = head; //指针p指向头结点
count = 0;
while(p != NULL && count < i-1) //查找第i-1个结点
{
p = p->next;
count++;
}
if(p == NULL)
return false; //没有找到第i-1个结点
else{
node = (Link)malloc(sizeof(Node)); //申请一个结点node
node->data = x;
node->next = p->next; //结点node插入结点p之后
p->next = node;
return true;
}
}
2.5 创建一个单链表——头插法
操作接口:Link newList(DataType a[], int n);
头插法:将待插入结点插在头结点的后面
template<class DataType>
Link newList(DataType a[], int n)
{
head = (Link)malloc(sizeof(Node)); // 创建头结点
head->next = NULL;
for(i=0;i<n;i++) //创建后续结点
{
node = (Link)malloc(sizeof(Node));
node->data = a[i];
node->next = head->next;
head->next = node;
}
return head;
}
2.6 创建一个单链表——尾插法
操作接口:Link newList(DataType a[], int n);
尾插法:将待插入结点插在终端结点的后面
Link newList(DataType a[], int n)
{
head = (Link)malloc(sizeof(Node)); // 创建头结点
head->next = NULL;
rear = head; //尾指针初始化
for(i=0;i<n;i++)
{
node = (Link)malloc(sizeof(Node));
node->data = a[i];
// 每次创建一个结点都把next域设为空,node->next = NULL;就不用写下面那一句,不会出现问题
rear->next = node;
rear = node;
}
rear->next = NULL; //很关键!!!
return head;
}
每次创建一个结点时,将结点数据域指针域全部初始化,指针域初始化为空,养成好的编程习惯!
2.7 单链表节点的删除
操作接口:bool deleteNode(Link head, DataType x);
bool deleteNode(Link head, DataType x)
{
if(head == NULL || head->next == NULL){ //链表为空表
return false;
}
p = head->next; //初始化,p、q两个指针一前一后
q = head;
while(p!=NULL){
if(p->data==x){ //找到x结点,删除这个结点并提前返回
q->next = p->next;
free(p);
return true;
}
else{ //p的data域不等于x,则继续向后找
q = p;
p = p->next;
}
}
//如果循环结束了,说明没有找到和x相等的结点
return false;
}
2.8 单链表的释放
操作接口:void clearLink(Link head);
void clearLink(Link head){
while(head->next !== NULL){
p = head;
head = head->next;
free(p);
}
}
3. 循环链表
将单链表的首尾相接,将终端结点的指针域由空指针改为指向头结点,构成单循环链表,简称循环链表。
循环链表的特点:没有明显的尾端 → 如何避免死循环?——修改循环条件
循环条件:(其他操作和单链表一样,除了循环退出条件)
p != NULL → p != head
p->next != NULL → p->next != head
4. 双向链表
在单链表的每个结点中再设置一个指向其前驱结点的指针域。(占用更多的存储空间)
结点结构:
-
data:数据域,存储数据元素
-
prior:指针域,存储该节点的前趋结点地址
-
next:指针域,存储该节点的后趋结点地址