目录
一、单链表的定义
1.什么是单链表
单链表要找到某一个位序的结点只能从第一个结点开始依次往后寻找,直到找到为止。所以不支持随机存取
2.用代码定义一个单链表
课本上的代码:
代码实现:
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义单链表结点类型
typedef struct LNode {
ElemType data; //每个结点存放一个数据元素
struct LNode* next; //指针指向下一个结点
//将struct LNode为LNode重命名为
//并且用LinkList表示这是一个指向struct LNode的指针
}LNode,*LinkList;
注意:
王道课程采用的写法是
举例:
3.不带头结点的单链表
代码实现:
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义单链表结点类型
typedef struct LNode {
ElemType data; //每个结点存放一个数据元素
struct LNode *next; //指针指向下一个结点
//将struct LNode为LNode重命名为
//并且用LinkList表示这是一个指向struct LNode的指针
}LNode,*LinkList;
//初始化一个空链表
bool InitList(LinkList &L) {
L = NULL;//空表,暂时还没有任何结点,防止脏数据
return true;
}
LNode* p = (LNode*)malloc(sizeof(LNode));
void test() {
LinkList L;//声明一个执行那个单链表的指针
InitList(L);
}
void main() {
}
4.带头结点的单链表
不带头结点的头指针指向的下一个结点就是实际用于存放数据的节点
带头结点头指针所指向的结点称为头结点,此头结点不存放实际的元素,头结点之后的下一个结点才会存放数据
二、插入和删除
1.按位序插入
(1)、带头结点
如果要在i=1处插入,这时就能体会到带头结点的好处
具体代码实现:
这两句不能颠倒
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode,*LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
int x;
L = (LinkList)malloc(sizeof(LNode));//创建头结点
LNode* s, * r=L;
scanf("%d",&x);
while (x!=9999)
{
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
}
r->next = NULL;
return L;
}
//在第i个节点插入元素e(带头结点)
bool ListInsert(LinkList& L, int i, ElemType e) {
if (i < 1) {
return false;
}
LNode* p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点
while (p != NULL && j < i - 1)//循环找到第i-1个结点
{
p = p->next;
j++;
}
if (p == NULL) {//i值不合法
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s; //将结点s连到p之后
return true; //插入成功
}
int Length(LinkList& L) {
int len = 0;
LNode* p = L;
while (p->next!=NULL)
{
p = p->next;
len++;
}
return len;
}
int main(){
LinkList L;
L = List_TailInsert(L);
int len = Length(L);
LNode* p = L;
printf("插入前的单链表:\n");
for (int i = 0; i < len; i++)
{
p = p->next;
printf("L[%d]=%d\n", i, p->data);
}
//-------------------------------------------------------------------
printf("插入后的单链表:\n");
ListInsert(L,2,8);
int len1 = Length(L);
LNode* a = L;
for (int i = 0; i < len1; i++)
{
a = a->next;
printf("L[%d]=%d\n", i, a->data);
}
}
运行结果:
(2).不带头结点
在最开始插入时需要特殊处理
代码演示:
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode,*LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
int x;
scanf("%d", &x);
L= (LinkList)malloc(sizeof(LNode));
L->data = x;
LNode* s, * r=L;
while (x!=9999)
{
scanf("%d", &x);
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
}
r->next = NULL;
return L;
}
//在第i个节点插入元素e(不带头结点)
bool ListInsert(LinkList& L, int i, ElemType e) {
if (i < 1) {
return false;
}
if (i == 1) {//插入第1个结点的操作与其他结点的操作不同
LNode* s = (LinkList)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s;//头指针指向新结点
return true;
}
LNode* p; //指针p指向当前扫描到的结点
int j = 1; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第1个结点
while (p != NULL && j < i - 1)//循环找到第i-1个结点
{
p = p->next;
j++;
}
if (p == NULL) {//i值不合法
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s; //将结点s连到p之后
return true; //插入成功
}
int Length(LinkList& L) {
int len = 0;
LNode* p = L;
while (p->next!=NULL)
{
len++;
p = p->next;
}
return len;
}
int main(){
LinkList L;
L = List_TailInsert(L);
int len = Length(L);
LNode* p = L;
printf("插入前的单链表:\n");
for (int i = 0; i < len; i++)
{
printf("L[%d]=%d\n", i, p->data);
p = p->next;
}
//-------------------------------------------------------------------
printf("插入后的单链表:\n");
ListInsert(L,1,8);
int len1 = Length(L);
LNode* a = L;
for (int i = 0; i < len1; i++)
{
printf("L[%d]=%d\n", i, a->data);
a = a->next;
}
}
运行结果:
(3).指定结点的后插操作
代码演示:
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode, * LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
int x;
L = (LinkList)malloc(sizeof(LNode));//创建头结点
LNode* s, * r = L;
scanf("%d", &x);
while (x != 9999)
{
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
}
r->next = NULL;
return L;
}
//后插操作:在p结点之后插入元素e
bool InsertNextNode(LNode *p,ElemType e) {
if (p == NULL) {
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
if (s == NULL) { //内存分配失败
return false;
}
s->data = e;//用结点s保存数据元素e
s->next = p->next;
p->next = s;//将结点s连接到p之后
return true;
}
//在第i个节点插入元素e(带头结点)
bool ListInsert(LinkList& L, int i, ElemType e) {
if (i < 1) {
return false;
}
LNode* p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点
while (p != NULL && j < i - 1)//循环找到第i-1个结点
{
p = p->next;
j++;
}
return InsertNextNode(p, e);
}
int Length(LinkList& L) {
int len = 0;
LNode* p = L;
while (p->next != NULL)
{
p = p->next;
len++;
}
return len;
}
int main() {
LinkList L;
L = List_TailInsert(L);
int len = Length(L);
LNode* p = L;
printf("插入前的单链表:\n");
for (int i = 0; i < len; i++)
{
p = p->next;
printf("L[%d]=%d\n", i, p->data);
}
//-------------------------------------------------------------------
printf("插入后的单链表:\n");
ListInsert(L, 2, 8);
int len1 = Length(L);
LNode* a = L;
for (int i = 0; i < len1; i++)
{
a = a->next;
printf("L[%d]=%d\n", i, a->data);
}
}
运行结果:
(4).指定结点的前插操作
但是如果不传头指针这个思路就没办法实现
另一种实现方式:
申请一个新的结点:
把这个结点作为p的后继结点:
复制原来p中的数字
再把e放到原来的p节点里边
代码演示:
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode, * LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
int x;
L = (LinkList)malloc(sizeof(LNode));//创建头结点
LNode* s, * r = L;
scanf("%d", &x);
while (x != 9999)
{
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
}
r->next = NULL;
return L;
}
//前插操作:在p结点之前插入元素e
bool InsertPriorNode(LNode *p,ElemType e) {
if (p == NULL) {
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode));
if (s == NULL) { //内存分配失败
return false;
}
s->next = p->next;
p->next = s;//新结点s连接到p之后
s->data = p->data;//将p中元素复制到s中
p->data = e;//p中元素覆盖为e
return true;
}
//在第i个节点插入元素e(带头结点)
bool ListInsert(LinkList& L, int i, ElemType e) {
if (i < 1) {
return false;
}
LNode* p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点
while (p != NULL && j < i)//循环找到第i个结点
{
p = p->next;
j++;
}
return InsertPriorNode(p, e);
}
int Length(LinkList& L) {
int len = 0;
LNode* p = L;
while (p->next != NULL)
{
p = p->next;
len++;
}
return len;
}
int main() {
LinkList L;
L = List_TailInsert(L);
int len = Length(L);
LNode* p = L;
printf("插入前的单链表:\n");
for (int i = 0; i < len; i++)
{
p = p->next;
printf("L[%d]=%d\n", i, p->data);
}
//-------------------------------------------------------------------
printf("插入后的单链表:\n");
ListInsert(L, 2, 8);
int len1 = Length(L);
LNode* a = L;
for (int i = 0; i < len1; i++)
{
a = a->next;
printf("L[%d]=%d\n", i, a->data);
}
}
运行结果:
2.按位删除
(1).带头结点
(2).指定结点的删除
如果要删除最后一个节点这种思路就会报错
代码演示:
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode, * LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
int x;
L = (LinkList)malloc(sizeof(LNode));//创建头结点
LNode* s, * r = L;
scanf("%d", &x);
while (x != 9999)
{
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
}
r->next = NULL;
return L;
}
//删除指定节点p
bool DeleteNode(LNode* p) {
if (p==NULL)
{
return false;
}
if (p->next==NULL)
{
return false;
}
LNode* q = p->next; //令q指向*p的后继结点
p->data = p->next->data; //和后继结点交换数据域
p->next = q->next; //将*q结点从链中“断开”
free(q); //释放后继结点的存储空间
return true;
}
bool ListDelete(LinkList& L, int i) {
if(i < 1) {
return false;
}
LNode* p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点
while (p != NULL && j < i)//循环找到第i个结点
{
p = p->next;
j++;
}
return DeleteNode(p);
}
int Length(LinkList& L) {
int len = 0;
LNode* p = L;
while (p->next != NULL)
{
p = p->next;
len++;
}
return len;
}
int main() {
LinkList L;
L = List_TailInsert(L);
int len = Length(L);
LNode* p = L;
printf("插入前的单链表:\n");
for (int i = 0; i < len; i++)
{
p = p->next;
printf("L[%d]=%d\n", i, p->data);
}
//-------------------------------------------------------------------
printf("插入后的单链表:\n");
ListDelete(L, 2);
int len1 = Length(L);
LNode* a = L;
for (int i = 0; i < len1; i++)
{
a = a->next;
printf("L[%d]=%d\n", i, a->data);
}
}
运行结果:
三、单链表的查找
1.按位查找
在写插入函数时可以将查找的函数封装然后拿来直接使用
代码演示:
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode, * LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
int x;
L = (LinkList)malloc(sizeof(LNode));//创建头结点
LNode* s, * r = L;
scanf("%d", &x);
while (x != 9999)
{
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
}
r->next = NULL;
return L;
}
//按位查找
LNode* GetElem(LinkList L, int i) {
LNode* p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点
while (p != NULL && j < i)//循环找到第i个结点
{
p = p->next;
j++;
}
return p;
}
int main() {
LinkList L;
L = List_TailInsert(L);
LNode * p = GetElem(L, 2);
printf("此结点的值为:%d", p->data);
}
运行结果:
2.按值查找
如果是struct类型,那就不能用!=或者=来判断是否相等了
代码演示:
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode, * LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
int x;
L = (LinkList)malloc(sizeof(LNode));//创建头结点
LNode* s, * r = L;
scanf("%d", &x);
while (x != 9999)
{
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
}
r->next = NULL;
return L;
}
LNode* LocateElem(LinkList L, ElemType e) {
LNode* p = L->next;
//从第一个结点开始查找数据域为e的结点
while (p != NULL && p->data!=e)//循环找到第i个结点
{
p = p->next;
}
return p;
}
int main() {
LinkList L;
L = List_TailInsert(L);
LNode *p = LocateElem(L, 2);
printf("此结点的值为:%d",p->data);
}
运行结果:
3.求表的长度
四、单链表的建立
1.尾插法
我们没有必要每次都从头开始往后找
可以设置一个指针,让指针指向表尾的最后一个结点,当我们在尾部插入一个新的数据元素的时候,只需要对对r这个结点进行一个后插操作就可以
后插操作:
课本给出的尾插法代码:
代码演示:
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义单链表
typedef struct LNode {
ElemType data; //每个结点存放一个数据元素
struct LNode* next; //指针指向下一个结点
//将struct LNode为LNode重命名为
//并且用LinkList表示这是一个指向struct LNode的指针
}LNode, * LinkList;
LinkList List_TaillInsert(LinkList& L) {//正向建立单链表
int x;
L = (LinkList)malloc(sizeof(LNode));//建立头结点
LNode* s, * r = L;//r为表尾指针
scanf("%d", &x);//输入结点的值
while (x!=9999) {
s = (LNode*)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;//r指向新的表尾结点
scanf("%d", &x);
}
r->next = NULL;//尾结点指针置空
return L;
}
//求表的长度
int length(LinkList L) {
int len=0;
LNode* p = L;
while (p->next!=NULL)
{
p = p->next;
len++;
}
return len;
}
int main() {
LinkList L;
L=List_TaillInsert(L);
int len = length(L);
LNode* p = L;
for (int i = 0; i <len; i++)
{
p = p->next;
printf("L[%d]=%d\n",i,p->data);
}
}
输出结果:
2.头插法
课本给出的代码:
如果去掉
那么头结点可能指向内存中的某一片神秘的区域
头插法可实现链表的逆置:
代码演示:
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义单链表
typedef struct LNode {
ElemType data; //每个结点存放一个数据元素
struct LNode* next; //指针指向下一个结点
//将struct LNode为LNode重命名为
//并且用LinkList表示这是一个指向struct LNode的指针
}LNode, * LinkList;
LinkList List_HeadInsert(LinkList& L) {//逆向建立单链表
int x;
LNode* s;
L = (LinkList)malloc(sizeof(LNode));//建立头结点
L->next = NULL;//初始为空链表
scanf("%d", &x);//输入结点的值
while (x!=9999) {
s = (LNode*)malloc(sizeof(LNode));//创建新结点
s->data = x;
s->next = L->next;
L->next = s;//将新结点插入表中,L为头指针
scanf("%d", &x);
}
return L;
}
//求表的长度
int length(LinkList L) {
int len=0;
LNode* p = L;
while (p->next!=NULL)
{
p = p->next;
len++;
}
return len;
}
int main() {
LinkList L;
L = List_HeadInsert(L);
int len = length(L);
LNode* p = L;
for (int i = 0; i <len; i++)
{
p = p->next;
printf("L[%d]=%d\n",i,p->data);
}
}
运行结果: