知识点一:链表基本概念
定义:链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构
特点:链表由一系列节点( 链表中每一个元素称为节点)组成,节点在运行时动态生成(malloc) ,每个节点包括两个部分:
1)存储数据元素的数据域 2)存储下一个节点地址的指针域
链表分类及分析:
知识点二:链表节点定义
构成:链表由一个个节点构成,每个节点一般采用
结构体的形式组织
//单向链表定义
typedef struct stu
{
//数据域(自定义)
int num;
char name[32];
float score;
//指针域
struct stu *next; //保存 下一个节点的地址
}STU;
//双向链表定义
typedef struct stu
{
int num;
char name[32];
float score;
struct stu *next;
struct stu *pre;
}STU;
知识点三:链表遍历
typedef struct stu
{
//数据域(自定义)
int num;
char name[32];
float score;
//指针域
struct stu *next; //保存 下一个节点的地址
}STU;
//单向链表遍历
void test()
{
STU data1 = {100, "德玛", 59};
STU data2 = {101, "小法", 79};
STU data3 = {102, "小泡", 49};
STU data4 = {103, "盲僧", 99};
STU data5 = {104, "快乐", 89};
//链表头
STU *head = NULL;
head = &data1;
data1.next = &data2;
data2.next = &data3;
data3.next = &data4;
data4.next = &data5;
data5.next = NULL;
pb = head;
while(pb != NULL)
{
printf("%d %s %f\n", pb->num, pb->name, pb->score);
//pb == head:即pb指向第一个节点,pb->next则为下一个节点地址
pb = pb->next; //pb指向下一个节点
}
}
//双向遍历
void print_link(STU *head)
{
//判断链表是否存在
if(head == NULL)
{
printf("link not found\n");
}
else //链表存在
{
STU *pb = head; //pb指向头结点
STU *pf = head‐>pre; //pf指向了尾节点
do
{
if(pb == pf) //相遇 只需要打印pf或pb中任何一个信息就够了
{
printf("num=%d,name=%s,age=%d\n", pb->num,pb->name,pb->age);
break;
}
printf("num=%d,name=%s,age=%d\n", pb->num,pb->name,pb->age);
printf("num=%d,name=%s,age=%d\n", pf->num,pf->name,pf->age);
pb = pb->next;//next方向的移动
pf = pf->pre;//pre方向的移动
}while( pb‐>pre != pf ); //pf和pb不能 擦肩而过
}
}
知识点四:链表插入
分析有无带头
1、链表第一个节点之前插入
main.c
#include<stdio.h>
#include<string.h>
#include "link.h"
void stu_help(void);
int main(int argc,char *argv[])
{
//定义一个链表头 注意 一定要赋值为NULL
STU *head = NULL;
stu_help();
while(1)
{
char cmd[32]="";
printf("请输入操作指令:");
scanf("%s",cmd);
if(strcmp(cmd, "help") == 0)
{
stu_help();
}
else if(strcmp(cmd, "insert") == 0)
{
STU tmp;
printf("请输入需要插入的数据:");
scanf("%d %s %f",&tmp.num, tmp.name, &tmp.score);
//将tmp数据 插入到head所指向的链表中
head = insert_link(head, tmp);
}
else if(strcmp(cmd,"print") == 0)
{
print_link(head);
}
else if(strcmp(cmd,"search") == 0)
{
printf("‐‐‐‐‐search‐‐‐‐‐‐\n");
}
else if(strcmp(cmd,"delete") == 0)
{
printf("‐‐‐‐‐delete‐‐‐‐‐‐\n");
}
else if(strcmp(cmd,"free") == 0)
{
printf("‐‐‐‐‐free‐‐‐‐‐‐\n");
}
else if(strcmp(cmd,"quit") == 0)
{
head = free_lin(head);
break;
}
}
return 0;
}
void stu_help(void)
{
printf("################################\n");
printf("#help:打印帮助信息 #\n");
printf("#insert:插入链表节点 #\n");
printf("#print:遍历链表节点信息 #\n");
printf("#search:查询链表节点 #\n");
printf("#delete:删除链表节点 #\n");
printf("#free:释放链表 #\n");
printf("#quit:退出 #\n");
printf("################################\n");
return;
}
link.c
#include<stdio.h>
#include<stdlib.h> //calloc
#include"link.h"
STU* insert_link(STU *head, STU tmp)
{
//1、从堆区申请一个待插入的节点空间
STU *pi = (STU *)calloc(1,sizeof(STU));
if(pi == NULL) //如果空间开辟成功则!=NULL
{
perror("calloc"); //打印错误信息
return head;
}
//2、将tmp的值 赋值 给*pi
*pi = tmp;
pi‐>next = NULL;//注意
//3、将pi插入到链表中
if(head == NULL) //链表不存在
{
head = pi;
}
else //链表存在(头部之前插入)
{
//1、让pi 指向就的头
pi->next = head;
//2、head指向新的头节点
head = pi;
}
return head;
}
void print_link(STU *head)
{
if(head == NULL) //链表不存在
{
printf("link not find\n");
return;
}
else
{
STU *pb = head;
while(pb != NULL)
{
printf("%d %s %f\n", pb->num, pb->name,pb->score);
//pb == head:即pb指向第一个节点,pb->next则为下一个节点地址
pb = pb->next; //pb指向下一个节点地址
}
}
return;
}
link.h
//防止头文件重复包含
#ifndef __LINK_H__
#define __LINK_H__
//链表节点类型 定义
typedef struct stu
{
//数据域
int num;
char name[32];
float score;
//指针域
struct stu *next;
}STU;
extern STU* insert_link(STU *head, STU tmp);
extern void print_link(STU *head);
#endif
2、链表最后一个节点插入
//链表的尾部插入
STU* insert_link(STU *head, STU tmp)
{
//1、申请待插入的节点5 STU *pi = (STU *)calloc(1,sizeof(STU));
if(pi == NULL)
{
perror(calloc);
return head;
}
//2、将tmp的数据 赋值到 *pi
*pi = tmp;
pi->next = NULL;
//3、将节点插入到链表的尾部
if(head == NULL) //链表不存在
{
head = pi;
return head;
}
else //链表存在
{
//a、寻找链表的尾节点
STU *pb = head;
while(pb->next != NULL) //如果不是尾节点
pb = pb‐>next; //pb就指向下一个节点
//b、用尾结点 pb 链接上 插入的节点pi
pb->next = pi;
return head;
}
return head;
}
3、链表有序插入
根据顺序插入选择一种方式插入:第一个、中间、最后一个插入
//链表的有序插入 以num的顺序为准(小‐‐‐>大)
STU* insert_link(STU *head, STU tmp)
{
//1、给待插入的节点pi 申请 堆区空间
STU *pi = (STU *)calloc(1,sizeof(STU));
if(pi == NULL)
{
perror("calloc");
return head;
}
//2、将tmp的内容 赋值给 *pi
*pi = tmp;
pi‐>next = NULL;
//3、链表节点pi的插入
if(head == NULL) //链表不存在
{
head = pi;
return head;
}
else //存在
{
//a、寻找插入点
STU *pb = head, *pf = head;
//pb->num 小于 pi->num 才会移动
while(pb->num < pi->num && pb->next != NULL)
{
pf = pb; //pf先保存pb地址
pb = pb‐>next; //pb-next即下一个节点地址赋给pb
}
//b、插入点的判断
if(pb->num >= pi->num) //头部 中部插入
{
if(pb == head) //头部之前插入
{
pi->next = head;
head = pi;
return head;
}
else //中部插入
{
pf->next = pi;
pi->next = pb;
return head;
}
}
else //尾部插入
{
pb->next = pi;
return head;
}
}
return head;
}
4、双向循环链表头部插入
//头部之前插入
STU* insert_link(STU *head, STU tmp)
{
//1、为插入的节点pi申请空间
STU *pi = (STU *)calloc(1, sizeof(STU));
//2、将tmp的值 赋值给 *pi7 *pi = tmp;
//3、判断链表是否存在
if(head == NULL) //链表不存在
{
head = pi;
pi->next = head;
pi->pre = head;
}
else //链表存在(头部之前插入)
{
pi->next = head; //新头结点的next指向旧的头结点
pi->pre = head->pre; //新头结点的pre指向尾结点
head->pre->next = pi; //让尾结点的next指向新的头结点
head->pre = pi; //旧的头结点pre指向新的头
head = pi; //head指向新的头
}
return head;
}
知识点五:链表查询
mian.c(整个程序在第一个节点之前插入中)
else if(strcmp(cmd, "search") == 0)
{
char name[64] = 0;
STU *ret = NULL;
printf("请输入要查找的用户名:");
scanf("%s", name);
ret = search_link(head, name);
if(ret != NULL)
{
prinf("num = %d, name = %s, score = %f\n", ret->num, ret->name, ret->score);
}
}
link.c
STU* search_link(STU *head, char *name)
{
//1、判断链表是否存在
if(head == NULL) //不存在
{
printf("link not found\n");
return NULL;
}
else //链表存在
{
STU *pb = head;
//逐个将节点中的name 和 name比较 如果不相等 pb=pb->next
while(strcmp(pb->name, name) != 0 && pb->next != NULL)
pb = pb‐>next;
//判断是否找到
if(strcmp(pb->name, name) == 0) //找到
return pb;
else //没找到
return NULL;
}
return NULL;
}
双向链表查询
STU* search_link(STU *head, char *name)
{
//判断链表是否存在
if(head == NULL)
{
return NULL;
}
else //链表存在
{
STU *pb = head;//指向头结点
STU *pf = head‐>pre;//指向的是尾节点
printf("pb = %p\n", pb);
printf("pf = %p\n", pf);
printf("pb‐>pre = %p\n", pb‐>pre);
while( (strcmp(pb->name, name) != 0) && (strcmp(pf->name, name) != 0) && (pb != pf) )
{
printf("##pb‐>name=%s##\n",pb‐>name);
printf("##pf‐>name=%s##\n",pf‐>name);
pb = pb->next; //next方向移动
pf = pf->pre; //pf方向移动
if(pb‐>pre == pf)
{
break;
}
}
if(strcmp(pb->name,name) == 0)
{
return pb;
}
else if(strcmp(pf->name,name) == 0)
{
return pf;
}
}
return NULL;
}
知识点六:删除链表节点
单向链表:删除思路与有序插入相似,可参考
main.c
else if(strcmp(cmd,"delete") == 0)
{
char name[32] = "";
printf("请输入将要删除的姓名:");
scanf("%s",name);
head = detele_link(head,name);
}
link.c
STU* detele_link(STU *head,char *name)
{
//1、判断链表是否存在
if(head == NULL) //不存在
{
printf("link not found\n");
return head;
}
else //存在
{
//2、寻找删除点
STU *pf=head, *pb = head;
while(strcmp(pb‐>name,name)!=0 && pb‐>next != NULL)
{
pf = pb;
pb = pb->next;
}
//3、找到删除点
if(strcmp(pb->name, name)==0) //找到删除点
{
//4、判断删除的位置
if(pb == head) //删除头结点
{
head = pb‐>next;
free(pb);
}
else //中部 或 尾部节点
{
pf->next = pb->next;
free(pb); //释放pb节点
}
printf("已成功删除%s的相关节点\n",name);
return head;
}
else //没找到删除点
{
printf("链表中没有%s相关的数据节点信息\n",name);
}
}
return head;
}
双向链表删除节点
STU* delete_link(STU *head, int num)
{
//判断链表是否存在
if(head == NULL)
{
printf("link not found\n");
return head;
}
else
{
STU *pb = head; //指向头节点
STU *pf = head‐>pre; //指向尾节点
//逐个节点寻找删除点
while((pb‐>num != num) && (pf‐>num != num) && (pf != pb))
{
pb = pb‐>next; //next方向移动
pf = pf‐>pre; //pre方向移动
if(pb‐>pre == pf)
break;
}
if(pb->num == num) //删除pb指向的节点
{
if(pb == head) //删除头节点
{
if(head == head->next) //链表只有一个节点
{
free(pb);
head = NULL;
}
else
{
head->next->pre = head->pre;
head->pre->next = head->next;
head = head->next;
free(pb);
}
}
else //删除中尾部节点
{
pb->pre->next = pb->next;
pb->next->pre = pb->pre;
free(pb);
}
}
else if(pf->num == num) //删除pf指向的节点
{
if(pf == head) //删除头节点
{
if(head == head‐>next) //链表只有一个节点
{
free(pf);
head = NULL;
}
else
{
head‐>next‐>pre = head‐>pre;62 head‐>pre‐>next = head‐>next;
head = head‐>next;
free(pf);
}
}
else //删除中尾部节点
{
pf‐>pre‐>next = pf‐>next;
pf‐>next‐>pre = pf‐>pre;
free(pf);
}
}
else
{
printf("未找到%d相关的节点信息",num);
}
}
return head;
}
知识点七:链表释放
main.c
else if(strcmp(cmd,"free") == 0)
{
head = free_lin(head);
}
link.c
STU* free_link(STU *head)
{
//1、判断链表是否存在
if(head == NULL) //不存在
{
printf("link not found\n");
retrun head;
}
else //存在
{
STU *pb = head;
//逐个节点释放
while(pb != NULL)
{
//head保存下一个节点的位置
head = pb->next;
//释放pb指向的节点
free(pb);
//pb指向head
pb = head;
}
printf("链表已经释放完毕\n");
return head;
}
return head;
}
知识点八:链表逆序
mian.c
else if(strcmp(cmd,"reverse") == 0)
{
head = reverse_link(head);
}
link.c
STU* reverse_link(STU *head)
{
//1、判断链表是否存在
if(head == NULL) //不存在
{
printf("link not found\n");
retrun head;
}
else//存在
{
//int *p,num; //p为int *, num为int
STU *pb,*pr; //pb为STU *,pr 为STU *
//pb保存head->next ( 原因head->next会置NULL)
pb = head->next;
//将head->next置NULL(原因:头节点变尾节点)
head->next = NULL;
while(pb != NULL)
{
//pr 保存pb->next ( 原因:pb->next会指向head)
pr = pb -> next;
//pb->next指向head ( 原因:逆转方向)
pb->next = head ;
//保存逆转方向的代码可以重复执行
head = pb;
pb = pr;
}
return head;
}
return head;
}
知识点九:链表排序
选择法排序(以
数组实现):
#include<stdio.h>
int main()
{
int arr[10] = {0};
int n = sizeof(arr)/sizeof(arr[0]);
int i = 0,j = 0,min = 0;
printf("请输入%d个int数据\n", n);
for(i = 0; i<n; i++)
{
scanf("%d",arr+i);
}
for(i = 0;i < n-1;i++)
{
for(min = i,j = min+1;j < n;j++)
{
if (arr[min] > arr[j])
min = j;
}
if (min != i)
{
int tmp = 0;
tmp = arr[i];
arr[i] = arr[min];
arr [min] = tmp;
}
}
for(i = 0;i < n;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
return 0;
}
选择法排序(以
单向链表实现):思路与数组排序相似,参考数组
void sort_link(STU *head)
{
//1、判断链表是否存在
if(NULL == head)
{
printf("link not found\n");
return;
}
else
{
STU *p_i = head; //i=0
while(p_i‐>next != NULL) //i<n‐1 外层循环
{
STU *p_min = p_i; //min = i;
STU *p_j = p_min‐>next; //j = min+1
while(p_j != NULL) //j<n 内层循环
{
//寻找成员num最小值的 节点
if(p_min->num > p_j->num) //if(arr[min] > arr[j])
p_min = p_j; //min = j
p_j = p_j->next; //j++
}
if(p_min != p_i) //min != i
{
//只交换数据域(1、节点内容整体交换 2、只交换指针域)
//1、节点内容整体交换(数据域交换第1次 指针域交换第1次)
STU tmp;
tmp = *p_i;
*p_i = *p_min;
*p_min = tmp;
//2、只交换指针域(指针域交换第2次)
tmp.next = p_i‐>next;
p_i‐>next = p_min‐>next;
p_min‐>next = tmp.next;
}
p_i = p_i->next; //i++
}
}
}