链表的学习记录

目录

一、单链表

1.要点

2.代码

3.老师留的思考题

4.例题

二、循环链表Circular Linked List

1.要点

2.代码

3.循环链表的例题

4.约瑟夫环

三、双向链表Double Linked List

 1.要点

2.代码

四、静态单链表Static Linked List


一、单链表

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中去。
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序和三三总有一个能跑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值