数据结构——链表

一.前言

链表:链表是一种物理上存储非连续,数据元素的逻辑顺序(内存存储位置不一定连续)通过链表中的指针链接次序,数显线性存储结构。

链表中的每一个元素称为一个节点,元素分为两部分:数据域、指针域

        数据域:通常表示节点所存储的相关数据,比如该节点的元素值等

        指针域:节点的连接则是通过指针进行,以指针存储下一个节点的地址,以此找到下一个节点,由于每一个指针不断存储着下一个节点的地址,所以这些连接构成了线性表——链表。

注意:每一个节点的定义动态生成(malloc)。

原因:malloc 是为指针指向的地方开内存,如果指针没有开辟空间,没有首地址,对指针进行操作时会发生运行错误。

二.链表的操作

      这里以单向链表为例。

1.链表的构成

typedef struct student{
    int num,id;
    char name[101];  // 以上均为数据域
    struct student *next;
}student;       // 链表的构成,next指针 指向下一个节点

2.链表的创建

①尾插法

        由指针域依次寻找下一个节点,直至找到链表尾端,然后将新节点加至链表尾端

void link_add(student **phead,student *pnew) //  链表的创建
{
    student   *pmov    =     *phead; 
        // /*定义指针*/    /*对二级指针取*的一级指针*/
    if(*phead==NULL)    // 如果起始点为空,则第一个输入点为起始点
    {
        *phead=pnew;
        pnew->next=NULL;
    }
    else
    {
        while(pmov->next!=NULL) pmov=pmov->next; //通过next指针寻找下一个节点地址
        pmov->next=pnew; //创立新节点pnew,并且 pmov的next指针指向pnew地址
        pnew->next=NULL;
    }    
    return ;
}

 ②头插法

 

void link_add(node **phead,node *pnew)
{
	node *pmov=*phead;  // 头插头插法 
	if(*phead==NULL) // 空指针时 
	{
		*phead=pnew;  
		pnew->next=NULL;
	 }
	else
	{	
		pnew->next=pmov;  
		*phead=pnew;
	}
	return ;
}

关于phead指针:在被函数调用的时候,为了改变头指针head的值,需要传递头指针的地址,即使用取地址符(&),因此在调用函数时,phead指针为二级指针(即对指针取地址)。所以,在定义pmov指针时,将其等于 *phead(赋为一级指针操作)

3.链表的释放

void link_free(student **phead)
{
    student *pb=*phead;
    while(*phead!=NULL)
    {
        pb=*phead;
        *phead=(*phead)->next; // 先将phead传至下一个节点后再释放pb
        free(pb);
        pb=NULL; // 释放后 pb = NULL
    }
}

4.链表节点的删除

首先找到需要删除的某个节点,同时记录该节点的前一个节点,删除该节点只需要前节点指向删除节点的后节点即可。

void link_delete(student **phead,int num)
{
    student *pf=*phead,*pb=*phead;
    if(*phead==NULL) return ;
    while(pb->num!=num && pb->next!=NULL)
    {
        pf=pb;  pb=pb->next; // pf 为 pb的上一个节点
    }
    if(pb->num==num)
    {
        if(pb==*phead) *phead=pb->next; //如果恰好删除头节点,需要将*phead指针向下一个节点移动
        else pf->next=pb->next;
    }
    return ;
}

5.链表的节点插入

图示,先找到插入的位置(即找到两个相邻指针,在其中插入一个新指针)

void link_insert(student **phead,student *pnew)
{
    student *pf=*phead,*pb=*phead;
    if(*phead==NULL) // 如果起始点为空,则插入的节点即为头节点
    {
        *phead=pnew; // 将头节点定义为 pnew
        pnew->next=NULL;
        return ;
    }
    //  链表中每个节点位置都有可能
    while(pnew->num>=pb->num && pb->next!=NULL) {pf=pb,pb=pb->next;} //寻找插入的地方
    if(pnew->num>=pb->num) // 找到
    {
        if(pb==*phead) // 如果插入在头节点前
        {
            pnew->next=pb;
            *phead=pnew;
        }
        else  // 插在中间
        {
            pf->next=pnew;
            pnew->next=pb;
        }
    }
    else  // 插在末尾
    {
        pb->=next=pnew;
        pnew->next=NULL;
    }
    return ;
}

 6.链表的查找和遍历

将这两个操作分成一组的原因:这两个操作并不需要改变头指针head的内容,通过形参调用,一级指针即可。

①查找

student *link_search(student *head,int num) // 链表查找
{
    student *pmov=head; //一级指针
    while(pmov!=NULL)
    {
        if(pmov->num==num)
        {
            return pmov;
        }
        else pmov=pmov->next;
    }
    return NULL;
}

②遍历

void link_print(student *head) // 遍历链表
{
    student *pmov=head;
    while(pmov!=NULL)
    {
        printf("%d %d \n",pmov->id,pmov->num);
        pmov=pmov->next;
    }
    return ;
}

三.哨兵节点

哨兵是一个哑对象,作用就是简化边界条件的处理。

在链表中设置一个对象 L.nil,该对象代表NIL,但具有和其他对象相同的各个属性。对于链表代码中出现的每一处NIL的引用,都代之以对哨兵的L.nil的引用,如图,将一个常规的单向链表转变为有哨兵的单向循环链表

此时插入操作变为(尾插法):

void link_add(node *phead,node *pnew) // phead 为哨兵指针 
{
	if(phead->next==NULL)  // 如果哨兵位下一个没有指针即空指针 
	{
		phead->next=pnew;
		pnew->next=NULL;
	}
	else
	{
		node *pmov=phead->next;
		while(pmov->next!=NULL)
		 	pmov=pmov->next;
		pmov->next=pnew;
		pnew->next=NULL; 
	}
	return ;
}

删除某个节点操作:

void link_delete(node *phead,int num) // phead 为哨兵指针 
{
	node *pmov=phead->next,*pf=phead->next;
	if(phead->next==NULL) return ;
	while(pmov->next!=NULL && pmov->key!=num)
		pf=pmov,pmov=pmov->next;
	if(pmov->key==num)
	{
		if(pmov==phead->next) phead->next=pmov->next; 
		else pf->next=pmov->next;
	}
	return ;
}

遍历指针操作,从phead->next 开始遍历

oid print(node *phead)
{
	node *pmov=phead->next;
	while(pmov!=NULL)
	{
		printf("%d ",pmov->key);
		pmov=pmov->next;
	}
	return ;
}

操作并没有简便很多,该判断表头的还是需要判断,可以与无哨兵指针相比较。

区别在于:哨兵指针调用时不需要二级指针(因为与哨兵指针的元素无关联)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值