小甲鱼笔记:数据结构——线性表(四)循环链表和双向链表,约瑟夫问题,魔术师发牌问题,判断一个单链表是否有环
作者:互联网
一、循环链表
1. 背景
-
单链表只能从头指向尾部,如果不从头结点出发,就无法访问到全部结点
-
将单链表中终端结点的指针端由空指针改为指向头结点,使整个单链表形成一个环
-
头尾相连的单链表就是单循环链表,简称循环链表
-
循环链表的单链表主要差异在于循环的判断空链表的条件上
if(head->next == head)
- 将终端结点用指针rear指示,找到终端结点的时间复杂度为O(1);
- 因为终端用rear表示,则开始结点应该为rear->next->next。时间复杂度也是O(1);
2.初始化循环链表
void ds_init(node **pNode)//**表示链表上的一个元素 如果
{
int item;
node *temp;
node *target;
printf("输入结点的值,输入0完成初始化\n");
while(1){
scanf("%d",&item);
fflush(stdin);
if(item == 0)
return;
if((*pNode) ==NULL){
/*循环链表中只有一个结点*/
*pNode =(node*)malloc(sizeof(struct CLinkList));
if(!(*pNode)){
exit(0);
(*pNode)->data = item;
(*pNode)->next =*pNode;
}
else{
/*找到next指向第一个结点的结点*/
for(target =(*pNode);target->next !=(*pNode);target =target->next)
;
/*生成一个新结点*/
temp = (node *)malloc(sizeof(struct CLinkList));
if(!temp)
exit(0);
temp->data = item;
temp->next = *pNode;
target->next =temp;
}
}
}
3. 循环链表的插入
/*链表存储结构的定义*/
typedef struct CLinkList{
int data;
struct CLinkList *next;
}node;
/*插入结点*/
/*参数:链表的第一个结点,插入的位置*/
void ds_insert(node **pNode,int i){
node *temp;
node *target;
node *p;
int item;
int j = 1;
printf("请输入要插入结点的值:");
scanf(”%d“,&item);
if(i =1){
//新插入的节点作为第一个节点
temp =(node *)malloc(struct CLinkList);
if(!temp)
exit(0);
temp->data =item;
/*寻找最后一个节点*/
for(target =(*pNode);target->next!=(*pNode);target =target->next)
;
temp->next =(pNode);
target->next =temp;
*pNode =temp;
}
else{
target =*pNode;
for(;j < (i-1);++j){
target=target->next;
}
//target指向第三个元素
temp = (node *)malloc(sizeof(struct CLinkList));
if(!temp)
exit(0);
temp->data =item;
p = target->next;
target->next = temp;
temp->next = p;
}
}
4. 循环链表的删除
void ds_delete(node **pNode,int i){
node *target;
node *temp;
int j = 1;
if(i == 1){
//删除的第一个结点
/*找到最后一个结点*/
for(target = *pNode;target->next !=*pNode;target = target->next)
;
temp =*pNode;
*pNode =(*pNode)->next;
target->next =*pNode;
free(temp);
}
else{
target= *pNode;
for(;j<i-1;++j){
target=target->next;
}
temp = target->next;
target->next = temp->next;
free(temp);
}
}
5. 返回结点所在的位置
int ds_search(node *pNode,int elem){
node *target;
int i = 1;
for(target = pNode;target->data !=elem && target->next !=pNode;++i){
target = target->next;
}
if(target->next == pNode)/*表中不存在改元素*/
return 0;
else
return 1;
}
二、约瑟夫问题
1.问题提出
- 一共有41个人围城一个圈,由第一个人开始报数,每报到第3个人的时候,该人就必须自杀,然后由下一个人报数。直到所有人都自杀为止。
2. 代码实现
#include <stdio.h>
#include <stdlib.h>
typeof struct node{
int data;
struct node *next;
}node;
node *create(int n){
node *p =NULL,*head;
head = (node *)malloc(sizeof (node ));
p = head;
node *s;
int i = 1;
if( 0 !=n ){
while( i <= n){
s = (node *)malloc(sizeof(node));
s->data = i++;
p->next = s;
p = s;
}
s->next = head->next;
}
free(head);
return s->next;
}
int main(){
int n = 41;
int m = 3;
int i;
node *p = create(n);
node *temp;
m %=n;
while( p !=p->next){
for(i = 1;i <= m-1;i++){
p = p->next;
}
printf("%d->",p->next->data);
temp = p->next; //删除第m个节点
p->next = temp->next;
free(temp);
p = p->next;
}
printf("%d\n",p->data);
return 0;
}
三、循环链表的特点
-
在单链表中,我们有了头结点,可以用O(1)的时间访问第一个结点,但对于要访问最后一个结点,我们必须要挨个往下索引,所以需要O(n)的时间
-
我们可以用O(1)的时间可以由链表指针访问到最后一个结点
-
我们用指向终端结点的尾指针来表示循环链表
1、例题:实现将两个线性表(a1,a2,…,an)和(b1,b2,…,bm)连接成一个线性表()a1,…,an,b1,…,bm)的运算
-
分析
- 在单链表或头指针表示的单链表以上做这种链接操作,都需要遍历第一个链表,找到结点an,然后将结点b1链到到an的后面,其执行时间是O(n)
- 若在尾指针表示的单循环链表上实现,则只需要修改指针,无需遍历,其执行时间是O(1)
-
如图所示
2、代码实现
//假设A,B为非空循环链表的尾指针
LinkList Connect(LinkList A,LinktList B){
LinkList p = A-<next; //保存A表的头结点位置
A->next = B->next->next;//B表的开始结点连接到A表尾
free(B->next);//释放B表的头结点
B->next = p;
return B;//返回新循环链表的尾指针
}
四、判断单链表是否有环
-
方法一:
- 使用p,q两个指针,p总是向前走,但q每次都从头开始走,对于每个节点,看p走的步数是否和q一样。
- 如果,当p从6走到3时,用了6步,此时若q从head出发,则只需要两步到3,因而步数不等,出现矛盾,存在环
-
方法二:
- 使用p,q两个指针,p每次向前走一步,q每次向前走两步,若在某个时候p==q。则存在环。
#include "stdio.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;/*Status是函数的类型,其值是函数结果状态代码*/
typedef int ElemType;/*ElemType类型根据实际情况而定,这里假设为int*/
typedef struct Node{
ElemType data;
struct Node *next;
}Node,* LinkList;
/*初始化带头结点的空链表*/
Status InitList(LinkList *L){
*L = (LinkList)malloc(sizeof(Node));/*产生头结点,并使L指向此头结点*/
if(!(*L))/*存储分配失败*/
return ERROR;
(*L)->next = NULL;/*此指针域为空*/
return OK;
}
/*初始条件:顺序线性表L已存在,操作结果:返回L中数据元素个数*/
int ListLength(LinkList L){
int i = 0;
LinkList p = L->next;//p指向第一个结点
while(p){
i++;
p=p->next;
}
return i;
}
/*随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)*/
void CreateListHead(LinkList *L,int n){
ListList p;
int i;
srand(time); //初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));
(*L)->next =NULL; //建立一个带头结点的单链表
for(i = 0;i < n;i++){
p = (LinkList)malloc(sizeof(Node));
p->data = rand()%100 +1; //随机产生100以内的数字
p->next = (*L)->next;
(*L)->next = p; //插入到表头
}
}
/*随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)*/
void CreateListTail(LinkList *L,int n){
LinkList p,r;
int i;
srand(time(0)); //初始化随机数种子
*L=(LinkList)malloc(sizeof(Node)); //L为整个线性表
r = *L; //r为指向尾部的结点
for(i = 0;i < n ;i++){
p->(Node)malloc(sizeof(Node));
p->data = rand()%100+1;
r->next = p; //将表尾的终端节点的指针指向新结点
r = p; //将当前的新结点定义为表尾终端结点
}
r->next =(*L)->next->next;
}
//比较步数的方法
int HasLoop1(LinkList L){
LinkList cur1 = L;//定义结点 cur1
int pos1 = 0; //cur1 的步数
while(cur1){
LinkList cur2 = L; //定义结点cur2
int pos2 = 0;
while(cur2){
if(cur2 == cur1){
if(pos1 ==pos2) //走过的步数一样
break; //说明没有环
else{ //否则
printf("环的位置在第%d个结点处:\n\n",pos2);
return 1; //有环并返回1
}
}
cur2 = cur2->next;
pos2 ++; //cur2步数自增
}
cur1 = cur1->next; //cur1继续向后一个结点
pos1++; //cur1步数自增
}
return 0;
}
//利用快慢指针
int HasLoop2(LinkList L){
int step1 = 1;
int step2 = 2;
LinkList p = L;
ListList q = L;
while(p !=NULL && q !=NULL && q->next !=NULL){
p = p->next;
if(q->next !=NULL)
q = q->next->next;
printf(“p:%d,q:%d\n”,p->data,q->data);
if(p == q){
return 1;
}
return 0;
}
int main(){
LinkList L;
Status i;
char opp;
ElemType e;
int find;
int temp;
i = InitList(&L);
printf("初始化L后:ListLength(L)=%d\n",ListLength(L))
printf("\n1.创建有环链表(尾插法) \n2创建无循环链表(头插法)\n3.判断链表是否有环");
while( opp !=''0){
scanf("%c",&opp);
switch(opp){
case '1':
CreateListTail(&L.20);
printf("成功创建有环L(尾插法)\n");
printf("\n");
break;
case '2':
CreateListHead(&L,20);
printf("成功创建无环L(头插法)\n");
printf("\n");
break;
case '3':
printf("方法一:");
if(HasLoop1(L)){
printf("有环");
}
else{
printf("无环");
}
printf("方法二:");
if(HasLoop12(L)){
printf("有环");
}
else{
printf("无环");
}
}
}
}
}
五、双向链表
1. 概述
-
有一个单链表的顺序为
A->B->C->D->E->F
-
我们此时已经查找到D,再想要查找到C。由双向链表可知,
-
必须由
D->E->F->A->B->C
,过于麻烦 -
由此引入双向链表
2.双向链表的插入操作
- 代码实现
s->next = p;
s->prior = p->prior;
p->prior->next = s;
p->prior = s;
3. 双向链表的删除操作
- 代码实现
p->prior->next = p->next;
p->next->prior = p->prior;
free(p)
4. 小结
- 双向链表相对于单链表来说,是要更复杂一点,每个结点多了一个prior指针,对于插入和删除操作的顺序需要格外小心
- 双向链表有效算法的时间性能,即用空间换取时间
六、双向链表实战
- 要求实现用户输入一个数使得26个字母的排列发生变化
- 例如:用户输入3,输出结果为
- DEFGHIJKLMNOPQRSTUVWXYZABC
- 代码实现
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
typedef char ElemType;
typedef int Status;//状态返回
typedef struct DualNode;//定义双向循环链表
typedef struct DualNode{
ElemType data;
struct DualNode *prior;
struct DualNode *next;
}DualNode, *DuLinkList;
/*
*
* 初始化循环链表
*/
Status InitList(DuLinkList *L) {
DualNode *p, *q;
int i;
*L = (DuLinkList)malloc(sizeof(DualNode));
if (!(*L)) {
return ERROR;
}
(*L)->next = (*L)->prior = NULL;
p = (*L);
for ( i = 0; i < 26; i++)
{
q = (DualNode *)malloc(sizeof(DualNode));
if (!q)
{
return ERROR;
}
q->data = 'A' + i;
q->prior = p;
q->next = p->next; //p->next为NULL,让q->next = p->next ,此时q->next应当为NULL
p->next = q; //p->next 指向q结点
p = q; //让 p = q 让他们为同一个结点
}
p->next = (*L)->next;
(*L)->next->prior = p;
return OK;
}
void Caesar(DuLinkList *L, int i) {
if (i > 0)
{
do
{
(*L) = (*L)->next;
} while ( --i);
}
if (i < 0)
{
do
{
(*L) = (*L)->next;
} while (++i);
}
}
int main() {
DuLinkList L;
int i,n;
InitList(&L);
printf("请输入一个整数:");
scanf("%d",&n);
printf("\n");
Caesar(&L, n);
for ( i = 0; i < 26; i++)
{
L = L->next;
printf("%c",L->data);
}
return 0;
}
标签:结点,线性表,temp,int,next,链表,发牌,target 来源: https://blog.csdn.net/Mr_GYF/article/details/114824241