目录
一、单链表
1.要点
- 在第一个结点前设一个头结点,其指针域存储指向第一个结点的指针。若线性表为空,则头结点的指针域为空。
- 结点的两个区域:数据域和指针域(因为只有一个next指针域,所以叫单链表)。
- 初始化:没有返回值的话,就传指针的指针(头指针的地址),有返回值的话,操作指针返回就行。
2.代码
包括单链表的建立、结点插入、结点删除、按值查找、按位置查找、求长度、输出等等。
#include<iostream>
#include <cstdio>
#include <stdlib.h>
typedef int ElementType;//数据类型
typedef struct Node* PtrToNode;
struct Node {
ElementType Data; /* 数据域:存储结点数据 */
PtrToNode Next; /* 指针域:指向下一个结点的指针 */
};
typedef PtrToNode List; // 定义单链表类型 ,List就是Node*
void InitList(List *L)
{
//L是指向单链表头结点的指针,*L是单链表的头指针变量
*L = (List)malloc(sizeof(Node));//建立头结点
if (L)
{
(*L)->Next = NULL;//建立空的单链表L
}
}
void CreatFromHead(List L)//头插法建立单链表,逆序建表法,逻辑顺序和输入元素顺序相反
{
Node* s;
int num,input;
printf("请输入插入元素的个数:\t");
scanf("%d", &num);
printf("当前为头插法建立单链表,请输入n个元素\n");
while (num--)
{
scanf("%d", &input);
s = (Node*)malloc(sizeof(Node));//建立新节点,是一个结构体指针
s->Data =input;
s->Next = L->Next;//插入在开始,指向原本的下一个
L->Next = s;//取代原来的下一个成为头节点的下一个指向
}
}
void CreatFromTail(List L)//尾插法建立单链表,逆序建表法,逻辑顺序和输入元素顺序相反
{
Node* s;//用于申请新的结点并且赋值
Node* r=L;//动态指向链表当前的尾巴,方便做尾插入
int num,input;
printf("请输入插入元素的个数:\t");
scanf("%d", &num);
printf("当前为尾插法建立单链表,请输入n个元素\n");
while (num--)
{
scanf("%d", &input);
s = (Node*)malloc(sizeof(Node));//建立新结点,是一个结构体指针
s->Data = input;
r->Next =s;//原来的尾巴有了新的指向,指向刚刚创建的s
r = s;//新的尾巴变成了刚刚创建的s
}
r->Next = NULL;
}
Node* GetI(List L, int i)//在单链表L中查找第i个结点,返回结点的位置,找不到返回NULL
{
Node* find=L;
if (i <= 0)//不合理
return NULL;
int j = 1;
while (find->Next != NULL&&j<i)//找到链表的尾巴为止or找到了
{
find = find->Next;
j++;
}
if (i == j)
return find;
else
return NULL;
}
Node* GetByData(List L, ElementType key)//在单链表L中按值查找,返回结点的位置,找不到返回NULL
{
Node* find = L->Next;//从第一个结点开始找
while (find->Next != NULL )//找到链表的尾巴为止or找到了
{
if (find->Data == key)
return find;
find = find->Next;
}
return NULL;
}
int Get(List L)//求链表的长度
{
Node* find = L;
int longth = 0;
while (find->Next != NULL)//找到链表的尾巴为止or找到了
{
find = find->Next;
longth++;
}
return longth;
}
void InsertList(List L, int i, ElementType key)//在单链表L中的i位置插入key新结点
{
Node* now = L;
int j = 0;
while (j < i-1&&now->Next!=NULL)
{
now = now->Next;
j++;
}
if (now == NULL || i <= 0)//已经找完还没找到所求位置或者本来就不合理
{
printf("请求不合理\n");
return;
}
Node* s = (Node*)malloc(sizeof(Node));//申请新结点
s->Data = key;
s->Next = now->Next;//修改指针,插入
now->Next = s;
}
void DeleteList(List L, int i, ElementType *key)//删除第i个元素,并将元素保存在key中
{
Node* now = L;
int j = 0;
while (j < i - 1 && now->Next != NULL)
{
now = now->Next;
j++;
}
if (now->Next == NULL || i <= 0)//没有找到合法的前驱位置或者本来就不合理
{
printf("删除的请求不合理\n");
return;
}
Node* s = now->Next;//要删去的结点
now->Next = s->Next;
*key = s->Data;
free(s);//释放被删除结点所占的内存空间
}
void PrintList(List L)
{
Node* print = L;
while (print->Next!= NULL)
{
print = print->Next;
printf("%d ", print->Data);
}
printf("\n");
}
void DeleteK(List L, int i, int k)//从顺序表中删除自第i个元素开始的k个元素。不够k个元素时,全部删除。
{
Node* now =L;
int j = 0;
while (j < i - 1 && now->Next != NULL)
{
now = now->Next;
j++;
}
if (now->Next == NULL || i <= 0)//没有找到合法的前驱位置或者本来就不合理
{
printf("删除的请求不合理\n");
return;
}
int num = 0;
Node* Tail = now->Next;
while (Tail->Next != NULL && num < k)
{
Node* shan = Tail;
Tail=shan->Next;
num++;
free(shan);//释放被删除结点所占的内存空间
}
now->Next = Tail;
}
void DeleteAllKey(List L, ElementType key)//删除所有值相等的多余元素。要求时间复杂度为O(n),空间复杂度为O(1)
{
Node* find = L->Next;//从第一个结点开始找
while (find->Next!= NULL)//找到链表的尾巴为止
{
Node*now=find->Next;
if (now->Data == key)
{
Node* s = now;//要删去的结点
find->Next = s->Next;
free(s);//释放被删除结点所占的内存空间
}
find = find->Next;
}
}
int main()
{
List L;
InitList(&L);
//CreatFromHead(*L);
CreatFromTail(L);
printf("链表长度为:%d\n", Get(L));
PrintList(L);
printf("从i处开始连续删除k个元素,请输入i和k:\n");
int i,k;
scanf("%d%d",&i,&k);
DeleteK(L, i, k);
printf("链表长度为:%d\n元素为:", Get(L));
PrintList(L);
printf("删除链表中数据为k的所有元素,请输入k:\n");
scanf("%d",&k);
DeleteAllKey(L,k);
printf("链表长度为:%d\n元素为:", Get(L));
PrintList(L);
return 0;
}
3.老师留的思考题
1. 已知单链表L,指针p已经指向中间某个结点,是否可以在O(1)时间复杂度内删除p结点?
可以捏。
2. 已知单链表L,指针p已经指向中间某个结点,是否可以在O(1)时间复杂度内,在p结点前插入一个结点s?
可以捏ii
3. 已知单链表L,指针p已经指向某个结点,是否可以在时间复杂度O(1)内删除p结点?是否可以在平均时间复杂度O(1)内删除p结点?
如果p指向的是尾结点就是不可以的,得遍历。但是可以在平均时间复杂度O(1)内删除任意位置的p结点。
4.例题
有两个单链表LA和LB,其元素均为非递减有序排列,编写一个算法,将它们合并成一个单链表LC,要求LC也是非递减有序排列。要求:利用新表LC利用现有的表LA和LB中的元素结点空间,而不要额外申请结点空间。例如LA=(2,2,3),LB=(1.3.3,4),则LC=(1,2,2,3,3,3,4)
【算法思想】
要求利用现有的表LA和LB中的结点空间来建立新表LC可通过更改结点的next域来重建新的元素之间的线性关系。为保证新表仍然递增有序,可以利用尾插人法建立单链表的方法,只是新建表中的结点不用malloc,而只要从表LA和LB中选择合适的点插人到新表LC中即可
【代码】
#include<iostream>
#include <cstdio>
#include <stdlib.h>
typedef int ElementType;//数据类型
typedef struct Node* PtrToNode;
struct Node {
ElementType Data; /* 数据域:存储结点数据 */
PtrToNode Next; /* 指针域:指向下一个结点的指针 */
};
typedef PtrToNode List; // 定义单链表类型 ,List就是Node*
void InitList(List *L)
{
//L是指向单链表头结点的指针,*L是单链表的头指针变量
*L = (List)malloc(sizeof(Node));//建立头结点
if (L)
{
(*L)->Next = NULL;//建立空的单链表L
}
}
void CreatFromTail(List L)//尾插法建立单链表,逆序建表法,逻辑顺序和输入元素顺序相反
{
Node* s;//用于申请新的结点并且赋值
Node* r=L;//动态指向链表当前的尾巴,方便做尾插入
int num,input;
printf("请输入插入元素的个数:\t");
scanf("%d", &num);
printf("当前为尾插法建立单链表,请输入n个元素\n");
while (num--)
{
scanf("%d", &input);
s = (Node*)malloc(sizeof(Node));//建立新结点,是一个结构体指针
s->Data = input;
r->Next =s;//原来的尾巴有了新的指向,指向刚刚创建的s
r = s;//新的尾巴变成了刚刚创建的s
}
r->Next = NULL;
}
void Creat(List LA,List LB,List*LC)
{
Node *a=LA->Next;
Node *b=LB->Next;
Node *c=*LC;//动态指向链表C当前的尾巴,方便做尾插入
while(a!=NULL&&b!=NULL)
{
if(a->Data<=b->Data)//选择小的插入,对应链表的指针也往后走,也要更新尾巴
{
c->Next=a;
c=a;
a=a->Next;
}
else
{
c->Next=b;
c=b;
b=b->Next;
}
}
if(a)//如果a里还有元素,不管还有多少都是已经连接上来的,所以只用接个头就行
{
c->Next=a;
}
else
{
c->Next=b;
}
}
int Get(List L)//求链表的长度
{
Node* find = L;
int longth = 0;
while (find->Next != NULL)//找到链表的尾巴为止or找到了
{
find = find->Next;
longth++;
}
return longth;
}
void PrintList(List L)
{
Node* print = L;
while (print->Next!= NULL)
{
print = print->Next;
printf("%d ", print->Data);
}
printf("\n");
}
int main()
{
List LA,LB,LC;
InitList(&LA);
InitList(&LB);
InitList(&LC);
//CreatFromHead(*L);
printf("建立非递减LA链表:\n");
CreatFromTail(LA);
printf("LA链表长度为:%d\n", Get(LA));
PrintList(LA);
printf("建立非递减LB链表:\n");
CreatFromTail(LB);
printf("LB链表长度为:%d\n", Get(LB));
PrintList(LB);
printf("合并完成的LC链表:\n");
Creat(LA,LB,&LC);
printf("LC链表长度为:%d\n", Get(LC));
PrintList(LC);
return 0;
}
二、循环链表Circular Linked List
1.要点
- 首尾相接的链表,把最后一个结点的指针域改为指向头结点,得到循环单链表。
- 判断是否为表尾结点:p!=L 或者 p->next!=L
- 尾指针rear:开始结点为rear->next->next,终端结点为rear
2.代码
改动就是一点点,按照要点的一二改一改就成
void InitList(List *L)
{
//L是指向单链表头结点的指针,*L是单链表的头指针变量
*L = (List)malloc(sizeof(Node));//建立头结点
if (L)
{
(*L)->Next = *L;//建立空的循环单链表L
}
}
void CreatFromTail(List L)//尾插法建立单链表,逆序建表法,逻辑顺序和输入元素顺序相反
{
Node* s;//用于申请新的结点并且赋值
Node* r=L;//动态指向链表当前的尾巴,方便做尾插入
int num,input;
printf("请输入插入元素的个数:\t");
scanf("%d", &num);
printf("当前为尾插法建立单链表,请输入n个元素\n");
while (num--)
{
scanf("%d", &input);
s = (Node*)malloc(sizeof(Node));//建立新结点,是一个结构体指针
s->Data = input;
r->Next =s;//原来的尾巴有了新的指向,指向刚刚创建的s
r = s;//新的尾巴变成了刚刚创建的s
}
r->Next = L;//让最后一个结点的next指向头结点
}
3.循环链表的例题
有两个带头结点的循环单链表LAB,编写算法,将两个循环单链表合并为一个循
环单链表,其头指针为LA。
【算法思想】先找到两个链表LAB的表尾,并分别由指针p、q指向它们,然后将第一个链表的尾与第二个表的第一个结点链接起来,并修改第二个表的表尾q,使它的链域指向第一个表的头结点。
从头找到尾也太夸张了吧,如果单循环链表设置尾指针表示,我们在合并的时候直接修改尾结点的指针域就行,O(1)的执行时间捏~理解的图
List LinkTwoList1(List LRA,List LRB)//传来尾指针
{
Node *a=LRA->Next;//a现在代表a表的头
LRA->Next=LRB->Next->Next; //a的尾巴连上b的头
free(LRB->Next);//释放b的头结点
LRB->Next=a;//a的头连接到b的尾巴的指针域
}
4.约瑟夫环
题目:编号为1,2,…,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。一开始任选一个整数作为报数上限值m,从第一个人开始顺时针自1开始顺序报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有的人全部出列为止。试设计一个程序,求出出列顺序。
利用单向循环链表作为存储结构模拟此过程,按照出列顺序打印出各人的编号。
例如m的初值为20;n=7,7个人的密码依次是:3,1,7,2,4,8,4,出列的顺序为6,1,4,7,2,3,5。
说起来都是泪啊呜呜呜呜呜呜呜,长点心吧可
这块相当于没有头结点了,也可以理解为头结点就开始存东西了,然后我们传递的是尾指针,啊QAQ,不重要,都差不多。
#include<iostream>
#include <cstdio>
#include <stdlib.h>
typedef int ElementType;//数据类型
typedef struct Node* PtrToNode;
struct Node {
ElementType Data; /* 数据域:存储结点数据 */
int pos;
PtrToNode Next; /* 指针域:指向下一个结点的指针 */
};
typedef PtrToNode List; // 定义单链表类型 ,List就是Node*
int num;
List InitList()
{
int input,i=2;
printf("请输入参与游戏的总人数:\t");
scanf("%d", &num);
printf("请依次输入每人持有的密码\n");
//L是指向单链表头结点的指针,*L是单链表的头指针变量
List L = (List)malloc(sizeof(Node));//建立头结点
L->pos=1;
scanf("%d", &input);
L->Data=input;
Node* s;//用于申请新的结点并且赋值
Node* r=L;//动态指向链表当前的尾巴,方便做尾插入
for(int i=1;i<num;i++)
{
scanf("%d", &input);
s = (Node*)malloc(sizeof(Node));//建立新结点,是一个结构体指针
s->Data = input;
s->pos=i+1;
r->Next =s;//原来的尾巴有了新的指向,指向刚刚创建的s
r = s;//新的尾巴变成了刚刚创建的s
}
s->Next=L;
return r;//返回尾指针
}
void PrintList(List L)
{
Node* print = L->Next;
do
{
printf("%d-%d ", print->Data,print->pos);
print = print->Next;
}while (print!= L->Next);
printf("\n");
}
void YueSeFu(List L)
{
printf("从请输入报数上限值:\n");
int m;
scanf("%d",&m);
Node* now=L->Next;
Node* before=L;//记录上一个人
for(int i=1;i<num;i++)
{
for(int j=1;j<m;j++)
{
before=now;
now=now->Next;
}
printf("%d ",now->pos);
before->Next=now->Next;//交接仪式~
m=now->Data;
free(now);//刑满释放~
now=before->Next;
}
printf("%d",now->pos);
}
int main()
{
List L;
L=InitList();
printf("链表为:\n");
PrintList(L);
YueSeFu(L);
return 0;
}
三、双向链表Double Linked List
1.要点
- 指针域包括前驱和后继,这样形成的链表中就有两条方向不同的链
- 也可以有双向循环链表
- p->prior->next==p p==p->next->prior
2.代码
只涉及后继指针的算法与单链表相同,比如求长度,取元素,元素定位之类的,因为用哪条链都一样。重新来一份代码吧。
#include<iostream>
#include <cstdio>
#include <stdlib.h>
typedef int ElementType;//数据类型
typedef struct Node* PtrToNode;
struct Node {
ElementType Data; /* 数据域:存储结点数据 */
PtrToNode Next; /* 指针域:指向下一个结点的指针 */
PtrToNode Prior;//前驱指针
};
typedef PtrToNode List; // 定义单链表类型 ,List就是Node*
void InitList(List *L)
{
//L是指向单链表头结点的指针,*L是单链表的头指针变量
*L = (List)malloc(sizeof(Node));//建立头结点
if (L)
{
(*L)->Next = NULL;//建立空的单链表L
}
}
void CreatFromTail(List L)//尾插法建立单链表,逆序建表法,逻辑顺序和输入元素顺序相反
{
Node* s;//用于申请新的结点并且赋值
Node* r=L;//动态指向链表当前的尾巴,方便做尾插入
int num,input;
printf("请输入插入元素的个数:\t");
scanf("%d", &num);
printf("当前为尾插法建立单链表,请输入n个元素\n");
while (num--)
{
scanf("%d", &input);
s = (Node*)malloc(sizeof(Node));//建立新结点,是一个结构体指针
s->Data = input;
r->Next =s;//原来的尾巴有了新的指向,指向刚刚创建的s
s->Prior=r;
r = s;//新的尾巴变成了刚刚创建的s
}
r->Next = NULL;
}
void InsertList(List L, int i, ElementType key)//在双向链表L中的i位置插入key新结点
{
Node* now = L;
int j = 0;
while (j < i-1&&now->Next!=NULL)
{
now = now->Next;
j++;
}
if (now == NULL || i <= 0)//已经找完还没找到所求位置或者本来就不合理
{
printf("请求不合理\n");
return;
}
Node* s = (Node*)malloc(sizeof(Node));//申请新结点
if(s)
{
s->Data = key;
s->Next = now;//新的后继是右
s->Prior=now->Prior;//新的前驱是左,也是原来这个位置上的前驱
now->Prior->Next=s;//左节点的后继是新
now->Prior=s;//右节点的前驱是新
}
}
void DeleteList(List L, int i, ElementType *key)//删除第i个元素,并将元素保存在key中
{
Node* now = L;
int j = 0;
while (j < i - 1 && now->Next != NULL)
{
now = now->Next;
j++;
}
if (now->Next == NULL || i <= 0)//没有找到合法的前驱位置或者本来就不合理
{
printf("删除的请求不合理\n");
return;
}
Node* s = now->Next;//要删去的结点
s->Prior->Next=s->Next;
s->Next->Prior=s->Prior;
*key = s->Data;
free(s);//释放被删除结点所占的内存空间
}
void PrintList(List L)
{
Node* print = L;
while (print->Next!= NULL)
{
print = print->Next;
printf("%d ", print->Data);
}
printf("\n");
}
int Get(List L)//求链表的长度
{
Node* find = L;
int longth = 0;
while (find->Next != NULL)//找到链表的尾巴为止or找到了
{
find = find->Next;
longth++;
}
return longth;
}
int main()
{
List L;
InitList(&L);
//CreatFromHead(*L);
CreatFromTail(L);
printf("链表长度为:%d\n", Get(L));
PrintList(L);
printf("删除第k个元素,请输入k:\n");
int i,k;
scanf("%d",&k);
DeleteList(L,k,&i);
printf("删除的元素为%d\n",i);
printf("链表长度为:%d\n元素为:", Get(L));
PrintList(L);
printf("在位置i前插入k元素,请输入i、k:\n");
scanf("%d%d",&i,&k);
InsertList(L,i,k);
printf("链表长度为:%d\n元素为:", Get(L));
PrintList(L);
return 0;
}
四、静态单链表Static Linked List
之前的链表都是用指针实现的,结点空间的分配释放用到了malloc和free,属于动态链表。
静态链表是采用顺序存储结构数组模拟实现链表,使用“游标Cursor”来充当指针。
- 结构数组要开的大一点,作为结点的存储池
- 每个结点记录data和后继结点的游标(结构数组的下标值)
- 头结点space可以放在0位置,尾结点的游标指为-1.
- 关于可以使用的结点(未用过的和被删除过的)通过游标链形成一个空闲链表。插入时从中取得,删除时添加进去。就是跟已经使用过的结点一样,也设置一个头指针av。也就是说顺着space的游标指向可以看存放的结点,顺着av的游标可以看空闲的空间,当删除space的一个结点时,也要把它插入到av中去。