双链表实现--C语言版

目录

声明

以下内容仅供学习,如有侵权,联系作者删除。
参考文献:王道考研系列数据结构、B站up主:C语言技术网
链接: C语言技术网.

代码实现:

/*
 * 程序名:linklist4.c,此程序演示带头结点的双链表的实现,数据元素是整数。
 * 作者:jack 日期:20210710
 * 参考作者:C语言技术网(www.freecplus.net), B站UP主:C语言技术网
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef int ElemType;	// 自定义链表的数据元素为整数。

typedef struct DNode
{
	ElemType data;	// 存放结点的数据元素。
	struct DNode *prev,*next;	// 前驱和后继结点的指针。
}DNode, *DLinkList;

// 初始化链表DL,返回值:失败返回NULL,成功返回头结点的地址。
DNode *InitDList1();

// 清空链表DL
void ClearDList(DLinkList DL);

// 销毁链表DL。
void DestroyDList1(DLinkList DL);

// 在链表DL的第ii个位置插入元素ee,返回值:0-失败;1-成功。
int  InsertDList(DLinkList DL, unsigned int ii, ElemType *ee);

// 在链表LL的头部插入元素ee,返回值:0-失败;1-成功。
int  PushFront(DLinkList DL, ElemType *ee);

// 在链表LL的尾部插入元素ee,返回值:0-失败;1-成功。
int PushBack(DLinkList DL, ElemType *ee);

// 在指定结点pp之后插入元素ee,返回值:0-失败;1-成功。
int InsertNextNode(DNode *pp, ElemType *ee);

// 在指定结点pp之前插入元素ee,返回值:0-失败;1-成功。
int InsertPriorNode(DNode *pp, ElemType *ee);

// 删除链表DL中的第ii个结点,返回值:0-位置ii不合法;1-成功。
int  DeleteNode(DLinkList DL, unsigned int ii);

// 删除链表DL中第一个结点,返回值:0-位置不合法;1-成功。
int PopFront(DLinkList DL);

// 删除链表LL中最后一个结点,返回值:0-位置不合法;1-成功。
int PopBack(DLinkList DL);

// 删除指定结点。
int DeleteNode1(DNode *pp);

// 查找元素ee在链表DL中的结点地址,如果没找到返回NULL,否则返回结点的地址。
DNode *LocateElem(DLinkList DL, ElemType *ee);

// 获取链表中第ii个结点,成功返回结点的地址,失败返回空。
// 注意,ii可以取值为0,表示头结点。
DNode *LocateNode(DLinkList DL, unsigned int ii);

// 打印链表中全部的元素。
void PrintDList(DLinkList DL);

// 求链表的长度,返回值:>=0-表LL结点的个数。
int  LengthDList(DLinkList DL);

// 判断链表是否为空,返回值:0-非空或失败,1-空。
int IsEmpty(DLinkList DL);

int main()
{
	DLinkList DL = NULL; // 声明链表指针变量。

	DL = InitDList1();     // 初始化链表。

	printf("DL=%p\n", DL);

	ElemType ee;      // 创建一个数据元素。

	printf("在表中插入元素(1、2、3、4、5、6、7、8、9、10)。\n");
	ee = 1;  InsertDList(DL, 1, &ee);
	ee = 2;  InsertDList(DL, 1, &ee);
	ee = 3;  InsertDList(DL, 1, &ee);
	ee = 4;  InsertDList(DL, 1, &ee);
	ee = 5;  InsertDList(DL, 1, &ee);
	ee = 6;  InsertDList(DL, 1, &ee);
	ee = 7;  InsertDList(DL, 1, &ee);
	ee = 8;  InsertDList(DL, 1, &ee);
	ee = 9;  InsertDList(DL, 1, &ee);
	ee = 10; InsertDList(DL, 1, &ee);

	printf("length=%d\n", LengthDList(DL));	PrintDList(DL);

	printf("\n");
	printf("演示按位置插入元素。\n");
	printf("在第5个位置插入元素(13)。\n");
	ee = 13; InsertDList(DL, 5, &ee);	PrintDList(DL);

	printf("在表头插入元素(66),表尾插入元素(99)。\n");
	ee = 66; PushFront(DL, &ee);
	ee = 99; PushBack(DL, &ee);	PrintDList(DL);

	printf("\n");
	printf("演示按位置删除元素。\n");
	printf("删除表中第7个结点。\n");
	DeleteNode(DL, 7); PrintDList(DL);

	printf("删除表中第一个结点。\n");
	PopFront(DL); PrintDList(DL);

	printf("删除表中最后一个结点。\n");
	PopBack(DL); PrintDList(DL);

	printf("\n");
	printf("演示查找元素。\n");
	DNode *tmp;

	if ((tmp = LocateNode(DL, 3)) != NULL)
		printf("第3个结点的地址是=%p,ee=%d\n", tmp, tmp->data);
	else
		printf("元素值为8的结点的地址是NULL,没找着。\n");

	printf("\n");
	printf("演示按节点插入元素。\n");
	printf("在结点%p之后插入88\n", tmp);
	ee = 88;
	InsertNextNode(tmp, &ee);  PrintDList(DL);

	printf("在结点%p之前插入77\n", tmp);
	ee = 77;
	InsertPriorNode(tmp, &ee);  PrintDList(DL);

	printf("\n");
	printf("演示按节点删除元素。\n");
	// 找到第10个节点并删除它。
	if ((tmp = LocateNode(DL, 10)) != NULL)
		printf("第10个结点的地址是=%p,ee=%d\n", tmp, tmp->data);

	printf("删除结点%p\n", tmp);
	DeleteNode1(tmp);	PrintDList(DL);

	printf("\n");
	printf("演示销毁链表。\n");
	DestroyDList1(DL); DL = NULL;  // 销毁链表,LL置为空。
	printf("DL=%p\n", DL);

	return 0;
}

// 初始化链表DL,返回值:失败返回NULL,成功返回头结点的地址。
DNode *InitDList1()
{
	DNode *head = (DNode *)malloc(sizeof(DNode));	// 分配头结点。
	if (head == NULL)	return NULL;	// 内存不足,返回失败。

	head->next = head->prev = NULL;	// 前驱后继结点都置为空。

	return head;
}

void ClearDList(DLinkList DL)
{
	// 清空链表DL是指释放链表全部的结点,但不包括头结点。
	if (DL == NULL) { printf("链表LL不存在。\n"); return; } // 判断链表是否存在。

	DNode *tmp1;
	DNode *tmp2 = DL->next;	// 保留头结点,从头结点的下一个结点开始释放。

	while (tmp2 != NULL)
	{
		tmp1 = tmp2->next;
		free(tmp2);
		tmp2 = tmp1;
	}

	DL->next = NULL;	// 这行代码一定不能少,否则会留下野指针。
}

// 销毁链表DL。
void DestroyDList1(DLinkList DL)
{
	if (DL == NULL) { printf("链表LL不存在。\n"); return; } // 判断链表是否存在。

	DNode *tmp1;	// 销毁链表LL是指释放链表全部的结点,包括头结点。

	while (DL != NULL)
	{
		tmp1 = DL->next;
		free(DL);
		DL = tmp1;
	}
	// LL=NULL;   // LL在本函数中相当于局部变量,就算置空了也不会影响调用者传递的LL,
				  // 所以LL=NULL没有意义。
}

// 在链表DL的第ii个位置插入元素ee,返回值:0-失败;1-成功。
int  InsertDList(DLinkList DL, unsigned int ii, ElemType *ee)
{
	if ((DL == NULL) || (ee == NULL)) { printf("链表LL或元素ee不存在。\n"); return 0; } // 判断表和元素是否存在。

	// 判断插入位置是否合法
	if (ii < 1) { printf("插入位置(%d)不合法,应该在大于0。\n", ii); return 0; }

	// 要在位序ii插入结点,必须找到ii-1结点。
	DNode *pp = DL;  // 指针pp指向头结点,逐步往后移动,如果为空,表示后面没结点了。
	int kk = 0;      // kk指向的是第几个结点,从头结点0开始,pp每向后移动一次,kk就加1。

	while ((pp != NULL) && (kk < ii-1))
	{
		pp = pp->next;	kk++;
	}

	if (pp == NULL) { printf("位置(%d)不合法,超过了表长。\n", ii); return 0; }

	DNode *tmp = (DNode *)malloc(sizeof(DNode));	// 分配一个结点。
	if (tmp == NULL)	return 0;	// 内存不足,返回失败。

	// 考虑数据元素为结构体的情况,这里采用了memcpy的方法而不是直接赋值。
	memcpy(&tmp->data, ee, sizeof(ElemType));

	// 处理前驱后续结点的指针。
	tmp->next = pp->next;
	tmp->prev = pp;

	pp->next = tmp;
	if (tmp->next != NULL) tmp->next->prev = tmp;  // 特殊处理,如果是在尾部插入,tmp->next根本不存在。

	return 1;

	///
	// 以上代码可以用以下代码代替。
	// DNode *pp=LocateNode(LL,ii-1);  
	// return InsertNextNode(pp,ee);
	///
}

// 在链表LL的头部插入元素ee,返回值:0-失败;1-成功。
int  PushFront(DLinkList DL, ElemType *ee)
{
	return InsertDList(DL, 1, ee);
}

// 在链表LL的尾部插入元素ee,返回值:0-失败;1-成功。
int PushBack(DLinkList DL, ElemType *ee)
{
	DNode *pp = DL;	// 从头结点开始。

	while (pp->next != NULL)
	{
		pp = pp->next;
	}

	DNode *tmp = (DNode *)malloc(sizeof(DNode));	// 分配一个结点。
	if (tmp == NULL)	return 0;	// 内存不足,返回失败。

	// 考虑数据元素为结构体的情况,这里采用了memcpy的方法而不是直接赋值。
	memcpy(&tmp->data, ee, sizeof(ElemType));

	// 处理前驱后续结点的指针。
	tmp->next = pp->next;
	tmp->prev = pp;

	pp->next = tmp;

	return 1;
}

// 在指定结点pp之后插入元素ee,返回值:0-失败;1-成功。
int InsertNextNode(DNode *pp, ElemType *ee)
{
	if (pp == NULL) { printf("结点pp不存在。\n"); return 0; }

	DNode *tmp = (DNode *)malloc(sizeof(DNode));
	if (tmp == NULL)	return 0;

	memcpy(&tmp->data, ee, sizeof(ElemType));

	// 处理前驱后续结点的指针。
	tmp->next = pp->next;
	tmp->prev = pp;
	pp->next = tmp;
	if (tmp->next != NULL)	tmp->next->prev = tmp;	// 特殊处理,如果是在尾部插入,tmp->next根本不存在。

	return 1;
}

// 在指定结点pp之前插入元素ee,返回值:0-失败;1-成功。
int InsertPriorNode(DNode *pp, ElemType *ee)
{
	if (pp == NULL) { printf("结点pp不存在。\n"); return 0; }

	// 在单链表中,如果要在指定结点pp之前插入元素,采用的是偷梁换柱的方法。
	// 在双链表中,偷不偷都行,因为pp->prior有前驱结点的地址。

	DNode *tmp = (DNode *)malloc(sizeof(DNode));
	if (tmp == NULL) return 0;

	// 把待插入的元素存入tmp中。
	memcpy(&tmp->data, ee, sizeof(ElemType));

	// 处理前驱后继指针。
	tmp->prev = pp->prev;
	pp->prev->next = tmp;

	tmp->next = pp;
	pp->prev = tmp;

	return 1;
}

// 删除链表DL中的第ii个结点,返回值:0-位置ii不合法;1-成功。
int  DeleteNode(DLinkList DL, unsigned int ii)
{
	if (DL == NULL) { printf("链表LL不存在。\n"); return 0; } // 判断链表是否存在。

	// 判断删除位置是否合法
	if (ii < 1) { printf("删除位置(%d)不合法,应该在大于0。\n", ii); return 0; }

	DNode *pp = DL;
	int kk = 0;

	while ((pp != NULL) && (kk < ii - 1))
	{
		pp = pp->next;	kk++;
	}

	// 注意,以下行的代码与视频中的不一样,视频中的是 if ( pp==NULL ),有bug。
	if (pp->next == NULL) { printf("位置(%d)不合法,超过了表长。\n", ii); return 0; }

	DNode *tmp = pp->next;	// tmp为将要删除的结点。

	// 处理前驱后续结点的指针。
	pp->next = tmp->next;
	if (tmp->next != NULL) tmp->next->prev = pp; // 特殊处理,如果tmp是尾结点,tmp->next根本不存在。

	free(tmp);

	return 1;
}

// 删除链表DL中第一个结点,返回值:0-位置不合法;1-成功。
int PopFront(DLinkList DL)
{
	return DeleteNode(DL, 1);
}

// 删除链表LL中最后一个结点,返回值:0-位置不合法;1-成功。
int PopBack(DLinkList DL)
{
	if (DL == NULL) { printf("链表DL不存在。\n"); return 0; } // 判断表和元素是否存在。

  // 必须加上这个判断,不要误删了头结点。
	if (DL->next == NULL) { printf("链表LL为空,没有尾结点。\n"); return 0; } // 判断表是否为空。

	DNode *pp = DL;  // 从第0个结点开始。

  // 找到最后一个结点。
	while (pp->next != NULL) pp = pp->next;

	pp->prev->next = NULL;  // 把最后一个节点的前一结点的next指针置为空。

	// 释放最后一个结点。
	free(pp);

	return 1;
}

// 删除指定结点。
int DeleteNode1(DNode *pp)
{
	// 在单链表中,删除指定结点根本不可行,也不能采用偷梁换柱的方法。
	// 但是,在双链表中,删除指定结点是可行的。

	pp->prev->next = pp->next;
	if (pp->next != NULL)	pp->next->prev = pp->prev;	// 特殊处理,如果pp是尾结点,pp->next->prior根本不存在。

	free(pp);

	return 1;
}

// 查找元素ee在链表DL中的结点地址,如果没找到返回NULL,否则返回结点的地址。
DNode *LocateElem(DLinkList DL, ElemType *ee)
{
	DNode *pp = DL->next;

	while (pp)
	{
		// 如果数据元素是结构体,以下代码要修改。
		if (*ee == pp->data)	return pp;
		pp = pp->next;
	}

	return NULL;
}

// 获取链表中第ii个结点,成功返回结点的地址,失败返回空。
// 注意,ii可以取值为0,表示头结点。
DNode *LocateNode(DLinkList DL, unsigned int ii)
{
	if (DL == NULL) { printf("链表DL不存在。\n"); return NULL; } // 判断表和元素是否存在。

	DNode *pp = DL;
	int kk = 0;
	while ((pp != NULL) && (kk < ii))
	{
		pp = pp->next;	kk++;
	}

	if (pp == NULL) { printf("位置(%d)不合法,超过了表长。\n", ii); return NULL; }

	return pp;
}
// 打印链表中全部的元素。
void PrintDList(DLinkList DL)
{
	if (DL == NULL) { printf("链表DL不存在。\n"); return; } // 判断链表是否存在。

	DNode *pp = DL->next;  // 从第1个结点开始。

	while (pp != NULL)
	{
		printf("%-3d", pp->data);  // 如果元素ee为结构体,这行代码要修改。
		pp = pp->next;
	}

	printf("\n");

	/*
	  // 以下代码用于显示全部结点的地址和元素的值。
	  DNode *pp=DL;  // 从第0个结点开始。

	  while (pp != NULL)
	  {
		printf("pp=%p,prior=%p,next=%p,data=%-3d\n",pp,pp->prior,pp->next,pp->data);
		pp=pp->next;
	  }
	*/
}

// 判断链表是否为空,返回值:0-非空或失败,1-空。
int IsEmpty(DLinkList DL)
{
	if (DL == NULL) return 0;

	if (DL->next == NULL) return 1;

	return 0;
}

// 求链表的长度,返回值:>=0-表LL结点的个数。
int  LengthDList(DLinkList DL)
{
	if (DL == NULL) { printf("链表DL不存在。\n"); return 0; } // 判断链表是否存在。

	DNode *pp = DL->next;  // 头结点不算,从第1个结点开始。

	int length = 0;

	while (pp != NULL) { pp = pp->next; length++; }

	return length;

	// 不使用临时变量,如何计算链表(包括头结点)的长度?
	// if (LL==NULL) return 0;
	// return LengthList(LL->next)+1;
}

运行结果:
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值