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

一、双向链表

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

  • 数据域(存储数据)

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

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

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

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;
}
相关推荐
保持清醒54041 分钟前
二叉链表实现
数据结构
paeamecium1 小时前
【PAT甲级真题】- Recover the Smallest Number (30)
数据结构·算法·pat考试·pat
玛丽莲茼蒿1 小时前
Leetcode hot100 在排序数组中查找元素的第一个和最后一个位置【中等】
数据结构·算法
踩坑记录1 小时前
leetcode 92. 反转链表 II 区间反转(不是整条链表反转)
leetcode·链表
寒秋花开曾相惜2 小时前
(学习笔记)4.2 逻辑设计和硬件控制语言HCL(4.2.3 字级的组合电路和HCL整数表达式)
android·网络·数据结构·笔记·学习
发疯幼稚鬼2 小时前
二叉树的广度优先遍历
c语言·数据结构·算法·宽度优先
love在水一方2 小时前
【Voxel-SLAM】Data Structures / 数据结构文档(二)
数据结构·人工智能·机器学习
Via_Neo2 小时前
乘积最大问题
数据结构·算法
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 148. 排序链表 | C++ 归并排序自顶向下
c++·leetcode·链表
自我意识的多元宇宙2 小时前
数据结构----插入排序
数据结构·算法·排序算法