补充知识
链式存储结构存储线性表:把存储有数据元素的结点用指针域构造成链。
指针:指向物理存储单元地址的变量。
结点:一个由数据元素域及一个或若干个指针域组成的结构体。
单链表的定义
链式存储结构的线性表称为链表。根据指针域的不同和结点构造链的方法不同,链表主要有单链表、单循环链表和双循环链表三种。其中,单链表——构造链表的结点只有一个指向直接后继结点的指针域。
单链表的表示
单链表中每个结点的结构
单链表分为带头结点结构和不带头结点结构两种。
我们把指向单链表的指针称作头指针。头指针所指的不存放数据元素的第一个结点称作头结点。
带头结点的单链表
不带头结点的单链表
符号“^"表示空指针。空指针是一个特殊标记,用来表示链表的结束。
虽然看起来时连续的,但是链表的各个结点不一定是连续存储。(各个结点的地址不一定连续)
带头结点的单链表和不带头结点的单链表的使用根据实际需求来确定。
带头结点的单链表的操作实现
- 直接插入结点
//直接插入链表尾部
void AddList(SLNode *head, SLNode *node){
SLNode *temp = head; //头结点不能动,所以需要辅助遍历指针temp
while(temp->next != NULL) //遍历找到temp最后一个结点位置
temp = temp->next; //没有到最后就指向下一个结点位置
//当推出while循环时,temp指向了最后一个结点
//让temp的next指向待插入的结点
temp->next = node;
}
- 按照结点编号插入
带头结点的单链表添加结点示意图
//按照编号顺序插入链表
int AddByOrder(SLNode *head, SLNode *node){
SLNode *temp = head; //头结点不能动,所以需要辅助遍历指针temp
//temp->next == NULL时,直接插入最后即可。
//temp->next->no > node->no时,说明已经找到。
//这里要特别注意是 temp->next->no,而不是temp->no,因为 temp->no是头结点的,而头结点没有信息
while(temp->next != NULL && temp->next->no < node->no) {
temp = temp->next;
}
//判断temp->no是否等于node->no ,如果等于,则说明该编号结点已存在,不能插入
//要加上 temp->next != NULL 这个判断条件,否则,在链表为空时,temp->next->no会出现错误
if(temp->next != NULL && temp->next->no == node->no){
printf("编号为 %d 的结点已存在,不能插入\n", node->no);
return 0;
}
//否则,则可以插入链表
node->next = temp->next; //新的结点->next = temp->next
temp->next = node; //temp->next = 新的结点
return 1;
}
- 修改结点信息
//修改结点信息
int UpdateNode(SLNode *head, SLNode *newnode){
//根据结点 no 编号来修改,所以 no 编号不能动
SLNode *temp = head; //头结点不能动,所以需要辅助遍历指针temp
//查找待修改结点的前一个结点
//temp->next == NULL时,没有找到该节点
//这里要特别注意是 temp->next->no,而不是temp->no,因为 temp->no是头结点的,而头结点没有信息
while(temp->next != NULL && temp->next->no != newnode->no){
temp = temp->next;
}
//判断temp->no是否等于newnode->no ,如果等于,则说明该编号结点已找到
//要加上 temp->next != NULL 这个判断条件,否则,在链表为空时,temp->next->no会出现错误
if(temp->next != NULL && temp->next->no == newnode->no){
strcpy(temp->next->name, newnode->name);
return 1;
}else{
printf("没有找到编号为 %d 的结点\n", newnode->no);
return 0;
}
}
- 删除结点
带头结点的单链表删除结点示意图
//删除编号为 no 的结点
int DelNode(SLNode *head, int no){
/*方法一: */
//定义辅助指针 ,head暂时不能没有,故temp1指向head,temp2配合释放掉
SLNode *temp1 = head, *temp2 = NULL;
//查找待修改结点的前一个结点
//temp->next == NULL时,没有找到该节点
//这里要特别注意是 temp->next->no,而不是temp->no,因为 temp->no是头结点的,而头结点没有信息
while(temp1->next != NULL && temp1->next->no != no){
temp1 = temp1->next;
}
//判断temp->no是否等于no ,如果等于,则说明该编号结点已找到
//要加上 temp->next != NULL 这个判断条件,否则,在链表为空时,temp->next->no会出现错误
if(temp1->next != NULL && temp1->next->no == no){
temp2 = temp1->next; //temp2指向要删除的结点
temp1->next = temp1->next->next; //删除
free(temp2);
temp2->next = NULL;
return 1;
}else{
printf("没有找到编号为 %d 的结点\n", no);
return 0;
}
/*方法二:
SLNode *temp1 = head, *temp2 = NULL;
int j = 0;
while(temp1->next != NULL && temp1->next->next != NULL && j < no - 1){
temp1 = temp1->next;
j++;
}
//判断temp->no是否等于no ,如果等于,则说明该编号结点已找到
//要加上 temp->next != NULL 这个判断条件,否则,在链表为空时,temp->next->no会出现错误
if(j != no - 1){
printf("没有找到编号为 %d 的结点\n", no);
return 0;
}else{
temp2 = temp1->next; //temp2指向要删除的结点
temp1->next = temp1->next->next; //删除
free(temp2);
temp2->next = NULL;
return 1;
}
*/
}
- 显示单链表
//显示链表【遍历】
int ShowList(SLNode *head){
//定义temp辅助遍历
SLNode *temp = head->next;
//判断链表是否为空
if(head->next == NULL){
printf("链表为空\n");
return 0;
}
//遍历单链表
while(temp != NULL){
printf("[%d,%s]\n",temp->no,temp->name); //打印链表结点
temp = temp->next; //指向下一个结点
}
return 1;
}
- 撤销单链表
//撤销单链表
void Destroy(SLNode **head){
//定义辅助指针 ,head暂时不能没有,故temp1指向head,temp2配合释放掉
SLNode *temp1 = NULL, *temp2 = NULL;
//定义temp1辅助遍历
temp1 = *head;
while(temp1 != NULL){ //最终:temp1 = NULL,temp2被释放
temp2 = temp1; //temp2指向temp1,便于释放
temp1 = temp1->next; //temp1指向下一个,避免链表丢失
free(temp2); //释放temp2
temp2 = NULL; //避免free都成为“野指针”
}
*head = NULL; //最后让head = NULL
}
完整代码展示
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
//单链表的结点定义
typedef struct Node{
int no; //编号
char name[10]; //姓名
struct Node *next; //Node类型的next指针
}SLNode;
//单链表结点的初始化
void ListInitiate(SLNode **head){
*head = (SLNode *)malloc(sizeof(SLNode)); //申请头节点,由head指示其地址
(*head)->next = NULL; //置结束标记NULL
}
//求当前数据元素个数
int ListLength(SLNode *head){
SLNode *temp = head; //头结点不能动,temp指向head
int count = 0; //count初始化为0
while(temp->next != NULL){ //循环计数,temp->next == NULL时,指向单链表的最后一个元素
temp = temp->next; //指向下一个结点
count++; //计数器加1
}
return count;
}
//直接插入链表尾部
void AddList(SLNode *head, SLNode *node){
SLNode *temp = head; //头结点不能动,所以需要辅助遍历指针temp
while(temp->next != NULL) //遍历找到temp最后一个结点位置
temp = temp->next; //没有到最后就指向下一个结点位置
//当推出while循环时,temp指向了最后一个结点
//让temp的next指向待插入的结点
temp->next = node;
}
//按照编号顺序插入链表
int AddByOrder(SLNode *head, SLNode *node){
SLNode *temp = head; //头结点不能动,所以需要辅助遍历指针temp
//temp->next == NULL时,直接插入最后即可。
//temp->next->no > node->no时,说明已经找到。
//这里要特别注意是 temp->next->no,而不是temp->no,因为 temp->no是头结点的,而头结点没有信息
while(temp->next != NULL && temp->next->no < node->no) {
temp = temp->next;
}
//判断temp->no是否等于node->no ,如果等于,则说明该编号结点已存在,不能插入
//要加上 temp->next != NULL 这个判断条件,否则,在链表为空时,temp->next->no会出现错误
if(temp->next != NULL && temp->next->no == node->no){
printf("编号为 %d 的结点已存在,不能插入\n", node->no);
return 0;
}
//否则,则可以插入链表
node->next = temp->next; //新的结点->next = temp->next
temp->next = node; //temp->next = 新的结点
return 1;
}
//修改结点信息
int UpdateNode(SLNode *head, SLNode *newnode){
//根据结点 no 编号来修改,所以 no 编号不能动
SLNode *temp = head; //头结点不能动,所以需要辅助遍历指针temp
//查找待修改结点的前一个结点
//temp->next == NULL时,没有找到该节点
//这里要特别注意是 temp->next->no,而不是temp->no,因为 temp->no是头结点的,而头结点没有信息
while(temp->next != NULL && temp->next->no != newnode->no){
temp = temp->next;
}
//判断temp->no是否等于newnode->no ,如果等于,则说明该编号结点已找到
//要加上 temp->next != NULL 这个判断条件,否则,在链表为空时,temp->next->no会出现错误
if(temp->next != NULL && temp->next->no == newnode->no){
strcpy(temp->next->name, newnode->name);
return 1;
}else{
printf("没有找到编号为 %d 的结点\n", newnode->no);
return 0;
}
}
//删除编号为 no 的结点
int DelNode(SLNode *head, int no){
/*方法一: */
//定义辅助指针 ,head暂时不能没有,故temp1指向head,temp2配合释放掉
SLNode *temp1 = head, *temp2 = NULL;
//查找待修改结点的前一个结点
//temp->next == NULL时,没有找到该节点
//这里要特别注意是 temp->next->no,而不是temp->no,因为 temp->no是头结点的,而头结点没有信息
while(temp1->next != NULL && temp1->next->no != no){
temp1 = temp1->next;
}
//判断temp->no是否等于no ,如果等于,则说明该编号结点已找到
//要加上 temp->next != NULL 这个判断条件,否则,在链表为空时,temp->next->no会出现错误
if(temp1->next != NULL && temp1->next->no == no){
temp2 = temp1->next; //temp2指向要删除的结点
temp1->next = temp1->next->next; //删除
free(temp2);
temp2->next = NULL;
return 1;
}else{
printf("没有找到编号为 %d 的结点\n", no);
return 0;
}
/*方法二:
SLNode *temp1 = head, *temp2 = NULL;
int j = 0;
while(temp1->next != NULL && temp1->next->next != NULL && j < no - 1){
temp1 = temp1->next;
j++;
}
//判断temp->no是否等于no ,如果等于,则说明该编号结点已找到
//要加上 temp->next != NULL 这个判断条件,否则,在链表为空时,temp->next->no会出现错误
if(j != no - 1){
printf("没有找到编号为 %d 的结点\n", no);
return 0;
}else{
temp2 = temp1->next; //temp2指向要删除的结点
temp1->next = temp1->next->next; //删除
free(temp2);
temp2->next = NULL;
return 1;
}
*/
}
//显示链表【遍历】
int ShowList(SLNode *head){
//定义temp辅助遍历
SLNode *temp = head->next;
//判断链表是否为空
if(head->next == NULL){
printf("链表为空\n");
return 0;
}
//遍历单链表
while(temp != NULL){
printf("[%d,%s]\n",temp->no,temp->name); //打印链表结点
temp = temp->next; //指向下一个结点
}
return 1;
}
//撤销单链表
void Destroy(SLNode **head){
//定义辅助指针 ,head暂时不能没有,故temp1指向head,temp2配合释放掉
SLNode *temp1 = NULL, *temp2 = NULL;
//定义temp1辅助遍历
temp1 = *head;
while(temp1 != NULL){ //最终:temp1 = NULL,temp2被释放
temp2 = temp1; //temp2指向temp1,便于释放
temp1 = temp1->next; //temp1指向下一个,避免链表丢失
free(temp2); //释放temp2
temp2 = NULL; //避免free都成为“野指针”
}
*head = NULL; //最后让head = NULL
}
//测试
int main(int argc, char *argv[]) {
SLNode *head; //定义头指针变量
SLNode *node1, *node2, *node3, *newnode; //定义测试结点
//初始化各结点
ListInitiate(&head);
ListInitiate(&node1);
ListInitiate(&node2);
ListInitiate(&node3);
ListInitiate(&newnode);
//定义各结点
node1->no = 1;
strcpy(node1->name, "Lucy");
node2->no = 2;
strcpy(node2->name, "Jimy");
node3->no = 3;
strcpy(node3->name, "Zoma");
newnode->no = 3;
strcpy(newnode->name, "Candy");
printf("当前数据个数:%d\n", ListLength(head));
/*
//测试插入链表尾部
AddList(head, node1);
printf("当前数据个数:%d\n", ListLength(head));
AddList(head, node3);
printf("当前数据个数:%d\n", ListLength(head));
AddList(head, node2);
printf("当前数据个数:%d\n", ListLength(head));
*/
//按照编号插入
AddByOrder(head, node2);
printf("当前数据个数:%d\n", ListLength(head));
//测试插入一样编号的结点结果是否正确
AddByOrder(head, node2);
printf("当前数据个数:%d\n", ListLength(head));
AddByOrder(head, node1);
printf("当前数据个数:%d\n", ListLength(head));
AddByOrder(head, node3);
printf("当前数据个数:%d\n", ListLength(head));
//显示链表
printf("修改前链表信息如下:\n");
ShowList(head);
//测试修改结点信息
UpdateNode(head, newnode);
//显示链表
printf("修改后链表信息如下:\n");
ShowList(head);
//测试删除结点信息
DelNode(head, 2);
//显示链表
printf("删除后链表信息如下:\n");
ShowList(head);
//撤销单链表
Destroy(&head);
return 0;
}
本篇参考: