数据结构之线性表2:单链表

线性表相关基础知识详见之前的文章

数据结构之线性表1:顺序表 https://blog.csdn.net/qq_45629864/article/details/107889870

链表概念

以链式结构存储的线性表就是链表

存储结构:存储器中位置任意,逻辑上相邻的数据元素在物理上不一定相邻,可能连续可能不连续。

存取方式:顺序存取

结点分为数据域指针域

  • 数据域:存储元素数值
  • 指针域:存储后继结点的位置

由若干个结点由指针链组成一个链表


头指针指向第一个元素地址,并且由头指针唯一确定,因此单链表可以用头指针名字命名

最后一个结点指向空:p->next=NULL

辨析头结点和头指针:

  • 头指针:指向链表第一个结点的指针
  • 头结点:为了方便操作,在第一个元素之前附设的一个结点,可以啥都不存也可以存表长(统计表长时候头结点不计入)
  • 首元结点:链表中存储第一个数据元素的结点

也可以像这样:H是头指针(仅有指针域),A是首元结点。ps:这种方式不推荐,绝大多数都带头结点

链表类型

  • 单链表:结点只有一个指针域指向后继元素
  • 双链表:结点由两个指针域,指向前驱和后继
  • 循环链表:收尾相接的链表

存储类型描述

typedef struct Node{
	ElemType data;
    struct Node *next;
}Node, *LinkList;

由于next指向的变量是Node型,所以struct Node *next,和第一行一样,可以看做照着嵌套声明*next(就像指向int型声明用int *p一样),这个类型就叫Node

*Linklist 是指向结构结点的指针类型,定义的时候就不用给变量前面加 * 号了

Node *L 就等于Linklist L


更优的使用方法:将数据项都包进ElemType这一类型内,然后在链表结构体中使用。

定义链表和指针

定义链表:Linklist L

定义结点:*Node p

分辨空表

默认是带头结点的单链表,若L->next==NULL就是空表

建立单链表

尾插法

将申请的新结点放在上一个结点后面,类比正常排队

LinkList CreatListTail(LinkList L){//尾插法创建结点 
	LinkList L;
    Node *s,*r;			//创建指向结点的指针
	int flag=1;			//flag=1时正常输入,flag=0时结束输入 
	int c;
	r=L;
	while(flag){
		scanf("%d",&c);
		if(c!=99999){
			s=(Node*)malloc(sizeof(Node));
			s->data=c;
			r->next=s;		//		尾结点指向新结点,连接起来
			r=s;		//将尾指针移向最后结点
		}
		else{
			flag=0;
			r->next=NULL;
		}
	} 
}

主体解读:当flag=1,也就是c!=99999时候,申请Node类型的新结点,并用指针s指向,将输入值c放入s指向的新结点的数据域,然后让原本指向头结点的尾指针r,他的指针域指向新结点s(其实就是连起来),之后再将尾指针挪到新结点上(就能一直指向最后一个结点), 输入结束后,指向尾巴的指针r再将指针域指向NULL(完成尾指针该做的任务)。

头插法

将申请的新结点放在第一个结点前面(头结点之后),类比每个人都插队到最前面


LinkList CreatListHead(LinkList L){//头插法创建结点
	Node *s;
	int c;
	int flag=1;
	while(flag){
	 	scanf("%d",&c);
	 	if(c!=99999){
	 		s=(Node*)malloc(sizeof(Node));
	 		s->data=c;		
			s->next=L->next;		//新结点指向头结点指向的结点
			L->next=s;		//头结点指向新结点
		}
		else{
			flag=0;
		} 
	}
}

主体解读:当flag=1,也就是c!=99999时候,申请Node类型的新结点,并用指针s指向,将输入值c放入s指向的新结点的数据域,让新结点指向头结点指向的结点,完成连接,再把头结点指向改为指向新结点,完成新结点插入头结点和首元结点之间 输入结束后,直接完成。

求单链表长度

本质就是数结点个数,指针从第一个元素指到最后一个结点(最后一个结点.next=NULL)。

本题计算带头结点的单链表长度

int ListLength(LinkList L){		//返回长度
	Node *p;
    p=L->next;		//p指向第一个元素
    int i=0;
    while(p!=NULL){		//当所指不为NULL
    	p=p->next;		//指向下一个
        i++;
    }
    return i;
}

由于最后结点是隐式位置,不知道具体值,所以用while语句。

当指向NULL时,不进循环,不加i,保存下来前值。

查找算法

按序号查找

默认单链表带头结点,每找一个结点计数+1,到达需要的序号。

Node *GetNum(LinkList L,int e){	//在表L中查找序号为e的结点 
	int j=0;
	Node *p;
	p=L;	//指向头结点
	while((p->next!=NULL)&&j<e) 	//未到达表尾&&未数到
	{
		p=p->next;
		j++;
	}
	if(e==j){		//找到了 
		return p;
	}
	else
	{
		return NULL;		//i<=0或者i>表长
	}
} 

主体解读:在:**未到达表尾&&计数小于序号(没数到)**这两个条件同时满足时循环,每次指针后移一个结点,并计数器+1,移动一次记一个数。

需要注意:最初p指向头结点,p后移一位到第一个结点,j=1。

按值查找

一个一个结点找相同的值,并返回这个结点

Node *GetElem(LinkList L,int e){	//在表L中查找值为e的结点并返回 
	Node *p;
	p=L->next;		//指向首元结点 
	while(p!=NULL){		//数过尾结点后退出循环 
		if(e!=p->data){		//未找到就后移 
			p=p->next;
		}
		else
		return p;
	}
} 

插入操作

在带头结点的单链表L中的第i个元素之前插入一个数据元素e

LinkList Insert(LinkList L,int i,int e){//在表L的第i个元素之前插入值为e的结点 
	Node *p,*s;
	int k=0;
	p=L;
	while(p!=NULL&&k<i-1){		//因为i之前,所以数到i-1个停下来 
		p=p->next;
		k++;
	}
	if(p==NULL){		//若p都找到空了还没有找到位置i,则不合理
 		printf("插入位置不合适");
 		return ERROR;
	} 
	s=(Node*)malloc(sizeof(Node));
	s->data=e;				//将值放入新结点s 
	s->next=p->next;		//新结点指向定位指针p的下一个结点 
	p->next=s; 				//p指向新结点完成插入 
}

主体解读:首先使用类似上面的查找算法,定位插入位置i的前一位,同时检测插入位置是否合法。申请新结点s,将值e放入s->data,新结点s指向定位指针p的下一项,定位结点p指向s完成连接

删除操作

单链表L中删除第i个结点,并将内部元素保存出来

LinkList Dele(LinkList L,int i,int e){//删除表L中第i个结点,并将内部元素保存在e里面  
	Node *p,*r;
	int k=0;
	p=L;
	while(p->next!=NULL&&k<i-1){	//定位到被删除结点的前一个,不能定位在尾结点 
		p=p->next;
		k++;
	}
	if(k!=i-1){
    /*言下之意上面的循环中是因为p->next=NULL所以跳出,也就是说删除结点在尾结点后面,结点不存在*/		
		printf("删除结点的位置不合理");
		return 0;
	}
	r=p->next;			//r指向要被销毁的结点 
	p->next=r->next;	//i-1结点指向销毁结点后一个(跨过销毁结点) 
	e=r->data;
	free(r);			//释放空间 
}

主体解读:通过定位指针p定位到需要删除结点的前一项,但是需要注意不能定位到尾指针(尾指针下一项不存在),删除指针r指向定位指针p下一结点(需要删除的结点),定位指针p再指向r的下一结点(跨过需要删除的结点),将删除结点中元素保存出来,最后释放删除结点r。

求两集合之差

集合A用单链表LA表示,集合B用单链表LB表示,
求A-B,即属于A集合而不属于B集合的元素。(去除AB共有的元素)

void Difference(LinkList LA,LinkList LB){
	Node *pre,*p,*q;	//后面两个分别是LA,LB指示器
    Node *r;		//销毁指针
    pre=LA;			//当前处理结点的前一个结点(删除时使用)
    p=LA->next;		//当前处理结点
    
    while(p!=NULL)	//p未指向空
    {	q=LB->next;	//q定位在LB第一个结点
    	while(q!=NULL&&q->data!=p->data)	//q未指向空 且 所指结点的元素值不相同就循环
        {	q=q->next;}
        
        if(q!=NULL)	//上面的循环因为两值相等而退出,还没走完就找到相同的
        {	r=p;
           	pre->next=p->next;
           	p=p->next;
           	free(r);
        }
        else		//没有找到相同,正常后移
        {	pre=p;
            	p=p->next;
        }
    }    
}

整体思路是LA中删除操作,LB中查找操作,LA每定位一个结点,LB要走完全部结点。

主体解读:由于我们会删除LA中重复结点,所以p指向LA处理结点,pre指向p前一个结点,当p在LA中未指向空,则q在LB中从头查找,当q未指向空且没找到相同,q不断后移。若因为出现相同值而退出循环,则删除LA定位到的元素,否则p后移。

合并操作

两个递增的有序链表,将其合并成一个有序链表

void MergeList(LinkList &LA,LinkList &LB,LinkList &LC){
	Node *pa,*pb,*pc;
    pa=LA->next;		//pa指向LA第一个元素
    pb=LB->next;		//pb指向LB第一个元素
    pc=LC=LA;			//用LA的头结点作为LC的头结点
    while(pa!=NULL&&pb!=NULL)		//当这两个都没有数到尾结点
    {	if(pa->data<=pb->data)
    		{	pc->next=pa;		//pc接上较小结点
        		pc=pa;			//pc移动到较小结点
            	pa=pa->next;		//pa后移一位
    	    }
        else
        	{	pc->next=pb;		//pc接上较小结点
        		pc=pb;			//pc移动到较小结点
            	pb=pb->next;		//pb后移一位
        	}
    }
    if(pa!=NULL)
    	{	pc->next=pa;
        }
    else
    	{	pc->next=pb;
        }
	free(LB);	//释放LB的头结点
}

主体解读:和顺序表合并类似,但是LC

销毁操作

从头结点开始,依次释放所有结点

void Destroy(LinkList L){//从头销毁所有结点 
	Node *p;
	while(L!=NULL)
	{
		p=L;
		L=L->next;
		free(p);	
	}
}

将头指针不断后移,p定位并删除头指针的前一项

主体解读:当头指针L未指向空,p指向L的位置,L后移一位,删除p指向的结点,直到头指针L指向空

清空操作

清除链表L内所有结点,变成只有头指针和头结点的空链表

LinkList Clear(LinkList L){
	Node *p,*q;
	p=L->next;
    while(p){
    	q=p->next;
        free(p);
        p=q;
    }
    L->next=NULL;
}

主体思想:用q保存p的下一个位置,删除p后,两个一块前移,直到p指向空。

摘自: 王卓老师:www.bilibili.com/read/cv3285…

摘自:耿国华老师 www.bilibili.com/video/BV1kx…

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值