复习之数据结构_带头结点的单链表

带头结点的单链表

如果说,顺序表是逻辑相邻,物理也相邻的话,那么链表就是逻辑相邻,物理不一定相邻了。对应在计算机中,顺序表,是开创了一块连续的内存空间,用于存放一定的数据。那么链表开辟的内存空间就不一定是连续的、同一块空间了。

顺序表(不定长)示意图:
在这里插入图片描述
单链表(带头结点)示意图:
在这里插入图片描述

插入图解

对于插入来说,假定:要插入位置的结点为p;插入新的结点为n。那么到底是先p->next = n->next呢?还是先n->next = p->next呢?
对于这个问题,我们可以用如下图来理解:
在这里插入图片描述

头文件(.h)

接下来我们来看头文件:

#pragma once
// 带头结点的单链表,尾结点的指针域为NULL
// 头结点:开头的标识,类似旗帜,不存放数据且不参与运算
// 数据结点:存放数据

typedef struct Node
{
	int data;			// 保存数据
	struct Node* next;  // 下一个结点
}Node,*List;

// 初始化函数
void InitList(List plist);

// 判空
bool IsEmpty(List plist);

// 获取数据长度
int GetLength(List plist);

// 头插
bool Insert_head(List plist, int val);

// 尾插
bool Insert_tail(List plist, int val);

// 在plist中查找关键字key,找到返回目标地址,失败返回NULL
Node* Search(List plist, int key);

// 删除plist中的第一个key
bool DeleteVal(List plist, int key);

// 打印输出所有数据
void Show(List plist);

// 逆置(重中之重)
void Reverse(List plist);

// 清空数据
void Clear(List plist);

// 销毁动态内存
void Destroy(List plist);

代码文件(.cpp)

接下来就是代码的实现了:

#include <stdio.h>  // 输入输出
#include <stdlib.h> // 动态内存
#include <assert.h> // 断言
#include "list.h"

// 初始化函数
void InitList(List plist)
{
	assert(plist != NULL);
	if (plist == NULL) return;

	// 将头结点的next赋值为空
	plist->next = NULL;
}

// 判空
bool IsEmpty(List plist)
{
	assert(plist != NULL);
	if (plist == NULL) return false;

	// 如果头结点的next结点为NULL,说明没有一个数据结点,即为空
	return plist->next == NULL;
}

// 获取数据长度
int GetLength(List plist)
{
	assert(plist != NULL);
	if (plist == NULL) return -1;

	int count = 0;
	for (Node* p = plist; p->next != NULL; p = p->next)
	{
		count += 1;
	}
	return count;
}

// 头插
bool Insert_head(List plist, int val)
{
	assert(plist != NULL);
	if (plist == NULL) return false;

	Node* p = (Node*)malloc(sizeof(Node));
	p->data = val;

	// 先将原来头结点的next赋值给新结点的next
	p->next = plist->next;
	// 再将新结点赋值给头结点的next
	plist->next = p;

	return true;
}

// 尾插
bool Insert_tail(List plist, int val)
{
	assert(plist != NULL);
	if (plist == NULL) return false;

	// 创建新结点
	Node* p = (Node*)malloc(sizeof(Node));
	// 将值存放到新结点
	p->data = val;

	// 找尾结点
	Node* q;
	for (q = plist; q->next != NULL; q = q->next)
	{
		// 不用操作,跟着循环定位到最后一个结点即可
		;
	}

	// 将新结点p插入在尾结点q的后面
	// p->next = NULL; 与下方语句等价
	// 因为q本身就是最后一个结点,q->next == NULL
	p->next = q->next;
	q->next = p;

	return true;
}

// 在plist中查找关键字key,找到返回目标地址,失败返回NULL
Node* Search(List plist, int key)
{
	assert(plist != NULL);
	if (plist == NULL) return NULL;

	// 注意:起始条件p初始化成第一个数据结点而不是头结点
	for (Node* p = plist->next; p != NULL; p = p->next)
	{
		if (key == p->data)
		{
			return p;
		}
	}
	// 遍历了一遍没找到,即失败返回NULL
	return NULL;
}

// 查找key的前驱结点,成功返回key的前驱,失败返回NULL
static Node* SearchPrio(List plist, int key)
{
	assert(plist != NULL);
	if (plist == NULL) return NULL;

	// 注意:此处起始条件p初始化成头结点
	for (Node* p = plist; p->next != NULL; p = p->next)
	{
		if (key == p->next->data)
		{
			return p;
		}
	}
	return NULL;
}

// 删除plist中的第一个key
bool DeleteVal(List plist, int key)
{
	assert(plist != NULL);
	if (plist == NULL) return false;

	// p定位到要删除的key的前驱结点
	Node* p = SearchPrio(plist,key);
	if (p == NULL)
	{
		// 若查找失败,删除也失败
		return false;
	}

	// q指向要删除的结点
	Node* q = p->next;

	// 将q从链表中删除
	p->next = q->next;
	free(q);

	return true;
}

// 打印输出所有数据
void Show(List plist)
{
	assert(plist != NULL);
	if (plist == NULL) return;

	for (Node* p = plist->next; p != NULL; p = p->next)
	{
		printf("%d ",p->data);
	}
	printf("\n");
}

// 逆置(重中之重)
void Reverse(List plist)
{
	// 利用头插的思想
	assert(plist != NULL);
	if (plist == NULL) return;

	Node* p = plist->next;
	Node* q;

	plist->next = NULL;

	while (p != NULL)
	{
		q = p->next;

		//将p头插到plist中
		p->next = plist->next;
		plist->next = p;
		p = q;
	}
}

// 清空数据
void Clear(List plist)
{
	assert(plist != NULL);
	if (plist == NULL) return;

	plist->next = NULL;
}

// 销毁动态内存
void Destroy(List plist)
{
	assert(plist != NULL);
	if (plist == NULL) return;

	Node* p;
	while (plist->next != NULL)//还有第一个数据节点
	{
		p = plist->next;//p指向第一个数据节点
		plist->next = p->next;//将p从链表中剔除
		free(p);
	}
}

简单测试

我们给出以下的测试代码:

#include <stdio.h>
#include <iostream>
#include "list.h"

int main()
{
	Node head;
	InitList(&head);
	printf("头插:0-5\n");
	for (int i = 0; i < 5; i++)
	{
		Insert_head(&head, i);
	}
	Show(&head);
	Clear(&head);

	printf("尾插:0-5\n");
	for (int i = 0; i < 5; i++)
	{
		Insert_tail(&head, i);
	}
	Show(&head);

	printf("获取长度:%d\n", GetLength(&head));
	
	printf("请输入要查找的关键字:");
	// C语言的scanf不好用,我们这里用C++的cin
	int _key = 0;
	std::cin >> _key;
	std::cout << Search(&head, _key) << std::endl;

	printf("请输入要删除的值:");
	int del = 0;
	std::cin >> del;
	DeleteVal(&head, del);
	Show(&head);

	std::cout << "逆置链表:" << std::endl;
	Reverse(&head);
	Show(&head);

	Destroy(&head);

	return 0;
}

输出结果:
在这里插入图片描述
第一次我们给的查找关键字是:5

但是我们可以看到单链表中的数据只有0~4;所以,查找失败,返回NULL;我们把这个返回的值打印出来就是00000000
同样的,删除的时候,链表中也没有5,所以删除也是失败的,但是因为本来就没有这个数据,也可以某种程度上认为是删除成功。不影响后续的输出。

在这里插入图片描述
第二次我们给的查找关键字是:4

对比之前的结果我们可以看到,查找是成功的,返回了关键字4的地址;
同样的,下面删除的时候,因为链表中是有4的,所以删除也成功了;
最后的逆置也没有问题。

参考资料

【1】严蔚敏. 数据结构(C语言版). 北京:清华大学出版社,2009:30.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值