408考研-数据结构-线性表单链表

本文详细介绍了单链表的结构、特点,包括头结点的概念,以及如何通过头插法和尾插法创建链表。文章涵盖了链表的插入、删除、遍历、长度计算、查找操作,以及一些高级应用如拆分、删除最大节点、合并有序链表和去除重复元素。每个操作都配以相应的代码示例,展示了链表处理的基本技巧和常见问题解决方法。
摘要由CSDN通过智能技术生成

线性表的链式结构

存储思想:把任意的存储单元连接起存放数据

特点:

  • 逻辑次序和物理存放位置不一定相同,元素之间的指向用指针表示
  • 单向列表,只能顺序读取,从前向后,方便增加和删除

请添加图片描述

头结点:第一个元素(首结点)之前的一个类型相同的结点,头节点不放入数据
头指针:指向了第一个结点的指针
存储密度:结点数据本身占用的空间/结点占用的空间

#include <stdio.h>
#include <malloc.h>
typedef int ElemType;
typedef struct LNode  
{
	ElemType data;
	struct LNode *next;		//指向后继结点
} LinkNode,*LinkList;					//声明单链表结点类型
  • typedef struct Node LNode: 这个声明为名为 Node 的结构体类型创建了一个新的别名 LNode。这意味着之后在程序中你可以使用 LNode 代替 struct Node 来声明变量
    LNode node1; //定义了一个结点变量
  • typedef struct Node *LinkList*: 这个声明不仅为结构体类型创建了一个别名,而且是为其指针类型创建的别名。这里 LinkList 成为了指向 struct Node 类型的指针类型的别名
    LinkList list; 相当于 struct Node * list; //定义了一个指向结点的指针变量
  • 同样的struct LNode * next 定义了一个指向这个结构体类型的指针,直接使用“next”的话就是一个地址,使用“ *next ”就是使用指向的结点。前者是指针,指向作用,后者是变量,存放数据的;
    问:如果我修改“ next ”的值呢?会发生什么?
    答:那next 的指向就发生变化,就像你修改你的收货地址一样的
    问:那修改 * next呢?
    答:变化的就是指向的那个存储单元的值的变化。

基础代码

前情小知识:

  1. (LinkNode *&L): 这里的 LinkNode *&L 表示传入的是一个指向 LinkNode 结构体的引用。这意味着函数内部对 L 的修改会影响到调用者持有的原始指针变量。通常这种形式用于需要改变实参(即链表头指针)的情况,比如当删除操作会更改链表头部时。
  2. (LinkNode *L): 这里的 LinkNode *L 表示传入的是一个指向 LinkNode 结构体的普通指针副本。函数内部可以修改 L 指向的内容,但无法直接改变调用者持有的原始指针变量。因此,如果删除操作涉及到更新链表头节点,那么仅凭这个版本的函数是无法将新头节点地址返回给调用者的。
1.1创建
//初始化单链表
void InitList(LinkNode*& L)
{
	L = (LinkNode*)malloc(sizeof(LinkNode));  	//创建头结点
	L->next = NULL;
}
void CreateListF(LinkNode *&L,ElemType a[],int n)
//头插法建立单链表
{
	LinkNode *s;
	L=(LinkNode *)malloc(sizeof(LinkNode));  	//创建头结点
	L->next=NULL;
	for (int i=0;i<n;i++)
	{	
		s=(LinkNode *)malloc(sizeof(LinkNode));//创建新结点s
		s->data=a[i];
		s->next=L->next;			//将结点s插在原开始结点之前,头结点之后
		L->next=s;
	}
}

  • 头插法:元素序列和结点顺序是相反的,逆序的。
void CreateListR(LinkNode *&L,ElemType a[],int n)
//尾插法建立单链表
{
	LinkNode *s,*r;
	L=(LinkNode *)malloc(sizeof(LinkNode));  	//创建头结点
	L->next=NULL;
	r=L;					//r始终指向终端结点,开始时指向头结点
	for (int i=0;i<n;i++)
	{	
		s=(LinkNode *)malloc(sizeof(LinkNode));//创建新结点s
		s->data=a[i];
		r->next=s;			//将结点s插入结点r之后
		r=s;
	}
	r->next=NULL;			//终端结点next域置为NULL
}

请添加图片描述

注:

  • 尾插法:元素的顺序和结点的顺序是对应的,顺序的。
  • 需要创建一个尾指针r ,来定位链表的尾部,用来插入数据
1.2 插入
bool ListInsert(LinkNode *&L,int i,ElemType e)
{
	int j=0;
	LinkNode *p=L,*s;
	if (i<=0) return false;			//i错误返回假
	while (j<i-1 && p!=NULL)		//查找第i-1个结点p
	{	j++;
		p=p->next;
	}
	if (p==NULL)					//未找到位序为i-1的结点
		return false;
	else							//找到位序为i-1的结点*p
	{	s=(LinkNode *)malloc(sizeof(LinkNode));//创建新结点*s
		s->data=e;
		s->next=p->next;			//将s结点插入到结点p之后
		p->next=s;
		return true;
	}
}
  • 要注意:别把已知的结点变成未知,链接断开
1.3删除(重点)
bool ListDelete(LinkNode *&L,int i,ElemType &e)
{
	int j=0;
	LinkNode *p=L,*q;
	if (i<=0) return false;		//i错误返回假
	while (j<i-1 && p!=NULL)	//查找第i-1个结点
	{	j++;
		p=p->next;
	}
	if (p==NULL)				//未找到位序为i-1的结点
		return false;
	else						//找到位序为i-1的结点p
	{	q=p->next;				//q指向要删除的结点
		if (q==NULL) 
			return false;		//若不存在第i个结点,返回false
		e=q->data;
		p->next=q->next;		//从单链表中删除q结点
		free(q);				//释放q结点
		return true;
	}
}

1.4遍历
void DispList(LinkNode *L)
{
    LinkNode *p=L->next;  	
    while (p!=NULL)		//空链表跳过
    {	printf("%d ",p->data);
     p=p->next;
    }
    printf("\n");
}

int ListLength(LinkNode *L)
{
    LinkNode *p=L;int i=0;
    while (p->next!=NULL)
    {	i++;
     p=p->next;
    }
    return(i);
}
1.5 查询
//获取某个位置的元素值
bool GetElem(LinkNode *L,int i,ElemType &e)
{
	int j=0;
	LinkNode *p=L;
	if (i<=0) return false;		//i错误返回假
	while (j<i && p!=NULL)
	{	j++;
		p=p->next;
	}
	if (p==NULL)				//不存在第i个数据结点
		return false;
	else						//存在第i个数据结点
	{	e=p->data;
		return true;
	}
}

 //获取某个值的位置
int LocateElem(LinkNode *L,ElemType e)
{
	LinkNode *p=L->next;
	int n=1;
	while (p!=NULL && p->data!=e)
	{	p=p->next;
		n++;
	}
	if (p==NULL)
		return(0);
	else
		return(n);
}
1.6插入
bool ListInsert(LinkNode *&L,int i,ElemType e)
{
	int j=0;
	LinkNode *p=L,*s;
	if (i<=0) return false;			//i错误返回假
	while (j<i-1 && p!=NULL)		//查找第i-1个结点p
	{	j++;
		p=p->next;
	}
	if (p==NULL)					//未找到位序为i-1的结点
		return false;
	else							//找到位序为i-1的结点*p
	{	s=(LinkNode *)malloc(sizeof(LinkNode));//创建新结点*s
		s->data=e;
		s->next=p->next;			//将s结点插入到结点p之后
		p->next=s;
		return true;
	}
}
1.7删除
bool ListDelete(LinkNode *&L,int i,ElemType &e)
{
	int j=0;
	LinkNode *p=L,*q;
	if (i<=0) return false;		//i错误返回假
	while (j<i-1 && p!=NULL)	//查找第i-1个结点
	{	j++;
		p=p->next;
	}
	if (p==NULL)				//未找到位序为i-1的结点
		return false;
	else						//找到位序为i-1的结点p
	{	q=p->next;				//q指向要删除的结点
		if (q==NULL) 
			return false;		//若不存在第i个结点,返回false
		e=q->data;
		p->next=q->next;		//从单链表中删除q结点
		free(q);				//释放q结点
		return true;
	}
}

//直接销毁整个表
void DestroyList(LinkNode *&L)
{
	LinkNode *pre=L,*p=pre->next;
	while (p!=NULL)
	{	free(pre);
		pre=p;
		p=pre->next;
	}
	free(pre);	//此时p为NULL,pre指向尾结点,释放它
}

注:

  • 删除操作要比增加操作多使用一个工作指针q,用来指向被删除的结点,便于释放空间
  • 删除有三种情况,一种是正常的,一种是空表

应用

拆分单链表L,奇数顺序L1,偶数逆序L2

void split(LinkNode *&L,LinkNode *&L1,LinkNode *&L2)
{	LinkNode *p=L->next,*q,*r1;	//p指向第1个数据结点
	L1=L;					//L1利用原来L的头结点
	r1=L1;					//r1始终指向L1的尾结点
	L2=(LinkNode *)malloc(sizeof(LinkNode));	//创建L2的头结点
	L2->next=NULL;			//置L2的指针域为NULL
	while (p!=NULL)
	{	r1->next=p;			//采用尾插法将结点p(data值为ai)插入L1中
		r1=p;
		p=p->next;			//p移向下一个结点(data值为bi)
		q=p->next;			//由于头插法修改p的next域,故用q保存结点p的后继结点
		p->next=L2->next;	//采用头插法将结点p插入L2中
		L2->next=p;
		p=q;				//p重新指向ai+1的结点
	}
	r1->next=NULL;			//尾结点next置空
}

注解:

  • 原来的单链表L,被拆分后留作奇数的单链表L1存在
  • 整个思想是把偶数项分离出去,剩下的奇数项相互连接起来,用指针r1始终指向单链表的尾部,来实现尾插法保证元素的顺序
  • p指针指向的是要操作的元素(被拆出去尾插到L2),而q指针需要始终保持p的后面,方便p指针尾插之后归位
  • r1指针需要移动到一个奇数元素项,靠的是p指针尾插完偶数项归位到q位置时,r1移动

删除单链表中元素最大的结点

void delmaxnode(LinkNode *&L)
{
	LinkNode *p=L->next,*pre=L,*maxp=p,*maxpre=pre;
	while (p!=NULL)					//用p扫描整个单链表,pre始终指向其前驱结点
	{
		if (maxp->data<p->data)		//若找到一个更大的结点
		{	maxp=p;					//更改maxp
			maxpre=pre;				//更改maxpre
		}
		pre=p;						//p、pre同步后移一个结点
		p=p->next;
	}
	maxpre->next=maxp->next;		//删除maxp结点
	free(maxp);						//释放maxp结点
}

注:

  • 删除结点操作需要两个指针, 记录最大结点并删除也需要两个指针

给定两个升序的单链表LA ,LB 合并成一个升序的单链表L

LinkNode* Merge_LinkList(LinkNode* La, LinkNode* Lb) {
	LinkNode* Lc, * pa, * pb, * pc, * ptr;
	Lc = La;		//把b表插入合并进a表
	pc = La;
	pa = La->next;
	pb = Lb->next;

	while (pa != NULL && pb != NULL) {  //尾插法插入
		if (pa->data < pb->data)
		{
			pc->next = pa;
			pc = pa;
			pa = pa->next;
		}
		else if (pa->data < pb->data)
		{
			pc->next = pb;
			pc = pb;
			pb = pb->next;
		}
		else
		{
			pc->next = pa;
			pc = pa;
			pa = pa->next;
			ptr = pb;
			pb = pb->next;
			free(ptr);
		}
	}
	if (pa != NULL) pc->next = pa;  //把剩下的结点所有都加上去
	else pc->next = pb;
	free(Lb);
	return Lc;
}

注解:

  • 升序合成升序,所以要用尾插法
  • 列表元素相比较的时候相等,其中一个插入,另一个释放空间
  • 在插入之后需要后移元素,然后继续比较,循环
  • 直到有一个表已经插入完成了,直接把另一个表直接接上

去除链表中的重复元素

void Delete_Node_value(LinkNode* L) {
    LinkNode* p = L->next, * q, * ptr;
    while (p != NULL) {
        q = p, ptr = p->next;
        //检查结点p的所有后继有没有相同的
        while (ptr != NULL) {
            if (ptr->data == p->data) {  //找到了就删除掉
                q->next = ptr->next;
                free(ptr);
                ptr = q->next;
            }
            else
            {
                q = ptr;
                ptr = ptr->next;
            }
        }
        p = p->next;
    }
}
  • 时间复杂度为n2
    请添加图片描述

综合

1.已知线性表中的元素以值递增有序的方式排列,并以单链表作存储结构。试设计一个高效的算法,删除表中所有值大于 mink 且小于 maxk 的元素(若表中存在这样的元素),同时释放被删除的结点空间。(注意, mink 和 maxk 是给定的两个参变量,它们的值可以和表中的元素相同,也可以不同。)

bool DeleteMiddleElem(LinkNode* L, int mink, int maxk) {
    if (mink > maxk) {
        printf("mink和maxk数据输入有误");
        return false;
    }
    LinkNode* p, * q, * pre = NULL;
    p = L;
    pre = p;
    p = p->next;

    while (p && p->data < maxk) {
        if (p->data <= mink)
        {
            pre = p;
            p = p->next;
        }
        else {
            pre->next = p->next;
            q = p;
            p = p->next;
            free(q);
        }
    }
    return true;
}

2.已知非空线性链表由 list指出,链结点的构造为(data,next)。请写一算法,将链表中数据域值最小的那个链结点移到链表的最前面。要求:不得额外申请新的链结点。

void minMoveFirst(LinkNode* L) {
    //创建两对指针,一对指向当前的,一对记录最小的
    LinkNode* min, * minpre, * p, * pre;
    //空表
    if (L == NULL) return;
    //寻找
    pre = L;
    p = L->next;
    min = p;
    minpre = pre;
    while (p != NULL) {
        if (p->data < min->data) {   //找到了比最小还小的
            minpre = pre;
            min = p;
        }
        pre = p;
        p = p->next;
    }
    //把最小的结点放到前面去
    minpre->next = min->next;
    min->next = L->next;
    L->next = min;
}

3.给定两个单链表(假设两个链表均不含有环)的头指针分别为head1和headz,请设计一个算法判断这两个单链表是否相交,如果相交就返回第一个交点,要求算法的时间复杂度为O(lengthl+length2),其中lengthl和length2分别为两个单链表的长度。

在这里插入图片描述

LinkNode* SearchFirst(LinkNode* L1, LinkNode* L2) {
	//定义两个扫描指针
	LinkNode* pa, * pb;
	pa = L1->next;
	pb = L2->next;

	if (pa == NULL || pb == NULL) {
		return NULL;
	}

	//让两指针开始从头开始同步走,直到相遇
	while (pa != pb) {
		if (pa == NULL) {
			pa = L2->next;
		}
		if (pb == NULL) {
			pb = L1->next;
		}
		pa = pa->next;
		pb = pb->next;
	}
	return pa;
}

注:

  • 这个题的想法是找到相交点,相交点后面的元素都是相同的,个数也是相同的。只有相交点前面的元素个数不同。
  • 两个单链表一般情况下一定:一个长,一个短;而我们的扫描指针是从头部开始的,这就引出思考,如何做才能让扫描指针指向相交点?并确定那就是相交点?
  • 想法:如果两个表一样长,那么当粮扫描指针走一样的步数,直到两个指针指向的结点相同,就可以确定那个结点就是相交点。so?如何让他们走一样长的路程呢?

在这里插入图片描述

答:短的走完x + L 这段结点路程,然后去走y这段;长的走完y+L这段路程去走 x这段。他们速度相同,路程相同,必定可以相遇,且刚好到达相交点

  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

沐沐沐沐沐雪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值