双向链表与循环链表基础操作&进阶操作

一、双向链表

双向链表是一种链式数据结构,其中每个节点包含三个部分:

  • 数据域(存储数据)

  • 前驱指针(指向上一个节点)

  • 后继指针(指向下一个节点)

与单向链表相比,双向链表可以向前和向后遍历,但需要额外的内存空间存储前驱指针。

1.创建链表

cs 复制代码
//1.创建头节点
node_t *create_doublelist(void)
{
	node_t *head = malloc(sizeof(node_t));
	if(head == NULL)
	{
		printf("%s:%s:%d:malloc fail\n",__FILE__,__func__ ,__LINE__);
		return NULL;
	}
	head -> next = NULL;
	head -> prev = NULL;

	return head;
}

2.头插

cs 复制代码
//2.头插
void doublelist_insert_head(node_t *phead ,data_t data)
{
	//创建新节点并给值
	node_t *p_new = malloc(sizeof(node_t));
	if(p_new == NULL)
	{
		printf("%s:%s:%d:malloc fail\n",__FILE__,__func__ ,__LINE__);
		return;
	}
	
	p_new -> data = data;
	p_new -> next = NULL;
	p_new -> prev = NULL;

	//新节点的next指向原先首节点
	p_new -> next = phead -> next;
	//新节点的prev指向头节点
	p_new -> prev = phead;
	//头节点的next指向新节点
	phead -> next = p_new;
	//如果链表长度大于1
	if(p_new -> next != NULL)
	{
		//原先首节点的prev指向新节点
		p_new -> next -> prev = p_new;
	}
}

3.尾插

cs 复制代码
//3.尾插
void doublelist_insert_tail(node_t *phead ,data_t data)
{
	//创建新节点并给值
	node_t *p_new = malloc(sizeof(node_t));
	if(p_new == NULL)
	{
		printf("%s:%s:%d:malloc fail\n",__FILE__,__func__ ,__LINE__);
		return;
	}

	p_new -> data = data;
	p_new -> next = NULL;
	p_new -> prev = NULL;

	//找到尾节点
	node_t *p = phead;
	while(p -> next != NULL)
	{
		p = p -> next;
	}
	//将新节点给到原先尾节点的next
	p -> next = p_new;
	//将尾节点的位置给到新节点的prev
	p_new -> prev = p;
}

4.判断链表是否为空

cs 复制代码
//4.判断链表是否为空
int is_empty(node_t *phead)
{
	if(phead == NULL)
	{
		printf("linklist is empty!\n");
		return -1;
	}
	return phead -> next == NULL;
}

5.遍历打印(可选择正逆序)

cs 复制代码
//5.遍历打印
int doublelist_print(node_t *phead,int dir)
{
	if(phead == NULL || is_empty(phead) == 1)
	{
		printf("empty linklist!\n");
		return -1;
	}

	if(dir == 1)
	{
		node_t *p = phead -> next;
		while(p != NULL)
		{
			printf("%d ",p -> data);
			p = p -> next;
		}
	}
	else if( dir == -1)
	{
		node_t *p = phead -> next;
		while(p -> next != NULL)
		{
			p = p -> next;
		}
		while(p != phead)
		{
			printf("%d ",p -> data);
			p = p -> prev;
		}
	}
	putchar('\n');
	return 0;
}
 

6.计算链表长度

cs 复制代码
//6.计算链表长度
int doublelist_long(node_t *phead)
{
	if(is_empty(phead) == 1)
	{
		return 0;
	}

	node_t *p = phead -> next;
	int cnt = 0;
	while(p != NULL)
	{
		cnt++;
		p = p -> next;
	}
	return cnt;
}

7.遍历查找

cs 复制代码
// 7.查找key并返回指向该节点的指针
node_t* doublelist_find_key(node_t *phead, data_t key)
{
    if(is_empty(phead) == 1)
    {
        printf("empty linklist!\n");
        return NULL;
    }
    
    node_t *p = phead -> next;
    while(p != NULL)
    {
        if(p -> data == key)
        {
            return p;  // 返回找到的节点指针
        }
        p = p -> next;
    }
    
    // 未找到
    printf("Key %d not found in linklist.\n", key);
    return NULL;
}

8.改值

cs 复制代码
//8.改值
node_t* doublelist_find_update(node_t *phead, data_t old, data_t new)
{
    if(is_empty(phead) == 1)
    {
        printf("empty linklist!\n");
        return NULL;
    }
    
    node_t *p = phead -> next;
    while(p != NULL)
    {
        if(p -> data == old)
        {
            p -> data = new;
			return p;  // 返回找到的节点指针
        }
        p = p -> next;
    }
    return NULL;
}

9.头删

cs 复制代码
//9.头删
int doublelist_delate_head(node_t *phead)
{
	if(is_empty(phead) == 1)
	{
		printf("%s: linklist empty!\n",__func__);
		return -1;
	}
	//记录首节点地址
	node_t *p = phead -> next;
	//头节点的next指向次节点
	phead -> next = p -> next;
	//将此节点的prev指向头节点,重新链接
	if(p -> next != NULL)
	{
		p -> next -> prev = phead;
	}
	//释放p所在的节点
	free(p);
	return 1;
}

10.尾删

cs 复制代码
//10.尾删
int doublelist_delete_tail(node_t *phead)
{
    if(is_empty(phead) == 1)
    {
        printf("%s: linklist empty!\n",__func__);
        return -1;
    }
    // 如果链表只有一个实际节点
    if (phead -> next -> next == NULL)
    {
        free(phead -> next);
        phead -> next = NULL;
        return 0;
    }

    // 找到尾节点的前一个节点
    node_t *p = phead -> next;
    while (p -> next != NULL)
    {
        p = p -> next;
    }
                                                                                                                                    
    // 现在p指向节点
    p -> prev -> next = NULL;  // 将倒数第二个节点的next置为NULL
    free(p);   // 释放尾节点
	return 1;
}

11.插删

cs 复制代码
//11.插删
int doublelist_delete_in(node_t *phead ,data_t key)
{
	if(phead == NULL)
	{
		printf("%s:%s:%d:phead NULL!\n",__FILE__,__func__ ,__LINE__);
		return -1;
	}
    if(is_empty(phead) == 1)
    {
        printf("%s: linklist empty!\n",__func__);
        return -1;
    }
	
    node_t *p = phead -> next; 
    while (p != NULL)
    {
		//判断是否找到目标节点
		if(p -> data == key)
		{
			p -> prev -> next = p -> next;
			//判断该节点是否为头节点或者尾节点
			if(p -> next != NULL)
			{
				p -> next -> prev = p -> prev;
			}
			//释放目标节点
			free(p);
			return 1;
		}
        p = p -> next;
    }
	return 0;
}

12.销毁链表

cs 复制代码
//12.销毁链表
int doublelist_destroy(node_t ** pphead)
{
	//1.每个节点都删掉
	//逐个释放
	//p为NULL表示有效的数据节点释放完
	if(is_empty(*pphead) == 1)
	{
		printf("%s: linklist empty!\n",__func__);
		return -1;
	}	
	
	node_t *p = (*pphead) -> next;
	node_t *temp = NULL;
	while( p != NULL)
	{
		temp = p;
		p = p -> next;
		free(temp);
	}
	
	//2.头节点释放掉
	free (* pphead);
	* pphead = NULL;//防止悬空指针
	
	return 1;
}

13.找中间位

cs 复制代码
//13.找中间位
node_t * doublelist_find_mid(node_t *phead)
{
	//空链表或只有头节点
	if(phead == NULL || phead -> next == NULL)
	{
		return NULL;
	}

	node_t * pfast = phead;
	node_t * pslow = phead;
	while(pfast != NULL && pfast -> next != NULL)
	{
		pfast = pfast -> next -> next;
		pslow = pslow -> next;
	}
	return pslow;
}

14.找倒数第K个节点

cs 复制代码
//14.找倒数第K个节点
node_t * doublelist_find_key_tail(node_t *phead, data_t key)
{
	//空链表或只有头节点
	if(phead == NULL )
	{
		printf("%s:phead is NULL\n",__func__);
		return NULL;
	}

	if(is_empty(phead) == 1)
	{
		printf("%s:linklist is empty!\n",__func__);
		return NULL;
	}

	node_t * pfast = phead;
	node_t * pslow = phead;
	int i = 0;
	for(i = 1 ; i <= key; i++ )
	{
		if(pfast == NULL)
		{
			return NULL;
		}
		pfast = pfast -> next;
	}

	while(pfast != NULL)
	{
		pfast = pfast -> next;
		pslow = pslow -> next;
	}
	return pslow;
}

二、循环链表

循环链表 是线性表链式存储结构的一种特殊形式,其特点是链表中最后一个结点的指针域指向头结点或第一个结点,使整个链表形成一个环。

循环链表可以分为两种:循环单链表和循环双链表。

  1. 循环单链表:

    在单链表中,最后一个节点的指针域指向头节点(注意:不是第一个节点,而是头节点。如果是不带头节点的链表,则指向第一个节点)。这样,整个链表形成一个环。

    循环单链表的操作与普通单链表类似,但在遍历时,循环的终止条件不再是节点指针为NULL,而是判断是否回到头节点(或开始节点)。

  2. 循环双链表:

    在双链表中,最后一个节点的后继指针指向头节点,而头节点的前驱指针指向最后一个节点,形成一个双向的环。

循环链表的优点:

  • 可以从任意节点出发遍历整个链表。

  • 在某些应用中,如需要循环轮转的场景,使用循环链表非常方便。

循环链表的操作注意点:

  • 在插入和删除节点时,需要特别注意更新指针,保持循环的特性。

  • 遍历时,注意循环的终止条件,避免死循环。

cs 复制代码
#include <stdlib.h>
#include <stdio.h>
#include "circlelist.h"

node_t *create_circlelist(void)
{
    //创建头节点
	node_t *head = malloc(sizeof(node_t));

	if (head == NULL)
	{
		printf("%s:malloc fail!\n",__func__);
		return NULL;
	}	
	//头节点的指针域指向自己
	head->next = head;

	return head;
}

/*
头插:
s1.创建新节点,填充数据 
s2.链接 
   a.p_new->next = phead->next;
   //找到头节点的上一个结点
   //p->next == phead->next
     b.p->next = p_new;

   //单个结点时 数据结点指向自己 
   if (p_new->next == phead)
   {
       p_new->next = p_new;
    }
	*/
node_t *circlelist_insert_head(node_t *phead,data_t data)
{
	if (phead == NULL)
	{
		return NULL;
	}

	node_t *p_new = malloc(sizeof(node_t));
	if (p_new == NULL)
	{
		return NULL;
	}

	p_new->data = data;
	p_new->next = phead->next;
	node_t *p = phead->next;
#if 0
	do
	{
		p = p->next;
		//printf("%d ",p);
	}while (p->next != phead->next);
#endif
#if 1
	while (p->next != phead->next)
	{
		p = p->next;
		//printf("%d ",p);
	}
#endif
	p->next = p_new;

	if (p_new->next == phead)
	{
		p_new->next = p_new;
	}
	return phead; 
}


int is_empty(node_t *phead)
{
	if (phead == NULL)
	{
		return -1;
	}

	return phead->next == phead;
}


int circlelist_print(node_t *phead)
{
	if (phead == NULL || is_empty(phead) == 1)
	{
		return -1;
	}

	node_t *p = phead->next;
	//node_t *p = phead;
	do
	{
		printf("%d ",p->data);
		p = p->next;
	}while (p!= phead->next);

	putchar('\n');
	return 0;
}

node_t *circlelist_find(node_t * phead,data_t k)
{
	if (phead == NULL || is_empty(phead) == 1)
	{
		return NULL;
	}

	node_t *p = phead->next;
	do
	{
		if (p->data == k)
		{
			return p;
		}
		p = p->next;
	}while (p != phead->next);

	return NULL;
}

int circlelist_delete(node_t *phead)
{
	if (phead == NULL || is_empty(phead) == 1)
	{
		return -1;
	}

	node_t *p = phead->next;
	node_t *p_temp = p;
	while (p->next != phead->next)
	{
		p = p->next;
	}

	phead->next = p_temp->next;
	p->next = p_temp->next;

	free(p_temp);

	return 0;
}

int circlelist_destroy(node_t **phead)
{
	if (phead == NULL || *phead == NULL)
	{
		return -1;
	}
   
	if (is_empty(*phead) == 1)
	{
		free(*phead);
		*phead = NULL;
		return 0;
	}else 
	{
		node_t *p = (*phead)->next->next;

		while (p != (*phead)->next)
		{
			node_t *p_temp = p;
			p = p->next;
			free(p_temp);
		}
		free(p);
		free(*phead);
		*phead = NULL;
	}

	return 0;
}
相关推荐
寄存器漫游者2 小时前
数据结构 单向链表进阶
数据结构·链表
秋深枫叶红2 小时前
嵌入式C语言阶段复习——函数
c语言·数据结构·算法
生命不息战斗不止(王子晗)2 小时前
2026面试大纲 - java数据结构与集合专题
java·数据结构·面试
青桔柠薯片2 小时前
数据结构:排序与算法
数据结构·排序算法
执着2592 小时前
力扣hot100 - 226、翻转二叉树
数据结构·算法·leetcode
-Try hard-2 小时前
排序和查找算法:插入排序、希尔排序、快速排序以及二分查找
数据结构·算法·排序算法
鲨辣椒100862 小时前
算法也能降低时间复杂度???—————算法延伸
数据结构·算法·排序算法
嵌入小生0072 小时前
数据结构 | 常用排序算法大全及二分查找
linux·数据结构·算法·vim·排序算法·嵌入式
小龙报3 小时前
【数据结构与算法】指针美学与链表思维:单链表核心操作全实现与深度精讲
c语言·开发语言·数据结构·c++·物联网·算法·链表