数据结构 - 单链表 C++ 实现
单链表
单链表的定义
typedef int ElemType;
typedef struct LNode {
ElemType data;
LNode *next;
} LNode, *LinkList;
此处 LNode 强调一个结点,*LinkList 强调一个单链表的头指针,本例中只有头指针使用 *LinkList ;
单链表的头指针和头节点
若单链表没有头节点,那么单链表的头指针则指向链表的第一个元素;若由头节点,头指针指向头节点;例如头指针为 L;如果链表为空,则有 L == NULL,若有头节点,则有 L->next = NULL;
注意此处的指向问题应当透彻理解指针的概念,指向理解为元素地址;此处的 L 为头指针;在没有头节点时指向第一个元素,L 就是第一个元素的地址,若没有元素,即没有第一个元素,那么 L == NULL;如果有头节点,那么 L 为头节点的地址,因此 L->next 即为元素的第一个结点,故当链表为空时 L->next == NULL;
本例中的单链表均为带头节点的单链表;
初始化一个单链表
初始化单链表的主要目的在于建立一个头节点,并让 L 指向头节点;
L 为指向结点的指针,动态申请的内存大小为 sizeof(LNode),L 的类型为 LinKList 或者说 LNode* 因此需要转化为 LinkList;代码如下:
void ListInitite(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
}
创建一个单链表
头插法
即将新元素插入到链表的第一个位置
void List_HeadInsert(LinkList &L) {
for(int i = 1; i <= 10; i++) { //将 1 ~ 10 按头插法插入单链表
LNode* p = (LNode*)malloc(sizeof(LNode));
p->data = i;
p->next = L->next;
L->next = p;
}
//按照头插法的插入方式结果为倒序
Show_List(L);
}
测试本段代码
#include<iostream>
#include<cstdlib>
using namespace std;
typedef int ElemType;
typedef struct LNode {
ElemType data;
struct LNode *next;
} LNode, *LinkList;
void ListInitite(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
}
void Show_List(LinkList L) {
LNode* p = L->next;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
}
void List_HeadInsert(LinkList &L) {
for(int i = 1; i <= 10; i++) { //将 1 ~ 10 按头插法插入单链表
LNode* p = (LNode*)malloc(sizeof(LNode));
p->data = i;
p->next = L->next;
L->next = p;
}
//按照头插法的插入方式结果为倒序
Show_List(L);
}
int main() {
LinkList L;
ListInitite(L);
List_HeadInsert(L);
return 0;
}
运行结果
10 9 8 7 6 5 4 3 2 1
尾插法
即新的元素放在链表尾
使用尾插法,需要定义一个尾指针 r,尾指针始终指向链表的最后一个元素;刚开始为空链表,尾指针指向头节点,即和 L 相等,此后每插入一个新的结点,新的结点成为新的尾结点,r 指向此结点;
void List_TailInsert(LinkList &L) {
LNode* r = L;
for(int i = 1; i <= 10; i++) {
LNode* p = (LNode*)malloc(sizeof(LNode));
p->data = i;
p->next = r->next;
r->next = p;
r = p;
}
}
测试:
#include<iostream>
#include<cstdlib>
using namespace std;
typedef int ElemType;
typedef struct LNode {
ElemType data;
struct LNode *next;
} LNode, *LinkList;
void ListInitite(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
}
void Show_List(LinkList L) {
LNode* p = L->next;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
}
void List_HeadInsert(LinkList &L) {
for(int i = 1; i <= 10; i++) { //将 1 ~ 10 按头插法插入单链表
LNode* p = (LNode*)malloc(sizeof(LNode));
p->data = i;
p->next = L->next;
L->next = p;
}
//按照头插法的插入方式结果为倒序
Show_List(L);
}
void List_TailInsert(LinkList &L) {
LNode* r = L;
for(int i = 1; i <= 10; i++) {
LNode* p = (LNode*)malloc(sizeof(LNode));
p->data = i;
p->next = r->next;
r->next = p;
r = p;
}
}
int main() {
LinkList L;
ListInitite(L);
List_TailInsert(L);
//按尾插法插入为顺序
Show_List(L);
return 0;
}
测试结果:
1 2 3 4 5 6 7 8 9 10
返回链表的长度
int Length(LinkList L) {
LNode* p = L;
int length = 0;
while (p->next) {
length++;
p = p->next;
}
return length;
}
链表的查询
按序号查找结点的值
即查找第 i 个结点的值,最终返回此结点
LNode* GetElem(LinkList L, int i) {
if(i == 0) {
return L;
}
if(i < 1 || i > Length(L)) { //若超出链表范围
return NULL;
}
LNode* p = L;
int now = 0;
while(p && now < i) {
p = p->next;
now++;
}
return p;
}
测试:
#include<iostream>
#include<cstdlib>
using namespace std;
typedef int ElemType;
typedef struct LNode {
ElemType data;
struct LNode *next;
} LNode, *LinkList;
void ListInitite(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
}
void Show_List(LinkList L) {
LNode* p = L->next;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
}
void List_TailInsert(LinkList &L) {
LNode* r = L;
for(int i = 1; i <= 10; i++) {
LNode* p = (LNode*)malloc(sizeof(LNode));
p->data = i;
p->next = r->next;
r->next = p;
r = p;
}
}
int Length(LinkList L) {
LNode* p = L;
int length = 0;
while (p->next) {
length++;
p = p->next;
}
return length;
}
LNode* GetElem(LinkList L, int i) {
if(i == 0) {
return L;
}
if(i < 1 || i > Length(L)) { //若超出链表范围
return NULL;
}
LNode* p = L;
int now = 0;
while(p && now < i) {
p = p->next;
now++;
}
return p;
}
int main() {
LinkList L;
ListInitite(L);
List_TailInsert(L);
LNode* ip = GetElem(L, 5);
Show_List(L);
if(ip) {
printf("\n%d", ip->data);
}
return 0;
}
测试结果:
1 2 3 4 5 6 7 8 9 10
5
按值查找结点
LNode* LocateElem(LinkList L, ElemType e) {
LNode* p = L->next;
while(p && p->data != e) {
p = p->next;
}
return p;
}
插入结点
在链表的第 i 个位置插入元素 e
插入新的元素后共有 len + 1 个元素,插入位置也必须在 [1, len + 1],因此插入位置必须在这个范围内;首先获得第 i - 1 个结点,然后进行操作;
bool ListInsert(LinkList &L, int i, ElemType e) {
if(i < 1 || i > Length(L) + 1) {
printf("插入位置错误\n");
return false;
}
LNode *pre, *s;
s->data = e;
pre = GetElem(L, i - 1);
s->next = pre->next;
pre->next = s;
return true;
}
测试代码:
#include<iostream>
#include<cstdlib>
using namespace std;
typedef int ElemType;
typedef struct LNode {
ElemType data;
struct LNode *next;
} LNode, *LinkList;
void ListInitite(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
}
void Show_List(LinkList L) {
LNode* p = L->next;
while (p) {
printf("%d ", p->data);
p = p->next;
}
}
void List_HeadInsert(LinkList &L) {
for(int i = 1; i <= 10; i++) { //将 1 ~ 10 按头插法插入单链表
LNode* p = (LNode*)malloc(sizeof(LNode));
p->data = i;
p->next = L->next;
L->next = p;
}
}
int Length(LinkList L) {
LNode* p = L->next;
int length = 0;
while(p) {
length++;
p = p->next;
}
return length;
}
LNode* GetElem(LinkList L, int i) {
if(i == 0) {
return L;
}
if(i < 1 || i > Length(L)) { //若超出链表范围
return NULL;
}
LNode* p = L;
int now = 0;
while(p && now < i) {
p = p->next;
now++;
}
return p;
}
bool ListInsert(LinkList &L, int i, ElemType e) {
if(i < 1 || i > Length(L) + 1) {
printf("插入位置错误\n");
return false;
}
LNode *pre = GetElem(L, i - 1);
LNode *s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = pre->next;
pre->next = s;
return true;
}
int main() {
LinkList L;
ListInitite(L);
List_HeadInsert(L);
ListInsert(L, 5, 100);
Show_List(L);
return 0;
}
测试结果:
10 9 8 7 100 6 5 4 3 2 1
前插和后插
前插即在一个已知结点的前面插入新的结点,后插即在一个已知结点的后面插入新的结点;
上面的插入函数即在结点的后面插入新的结点,首先需要得到第 i - 1 个结点,然后再此结点后面插入新的结点,即为后插;
前插操作也是类似,在某个结点的前面插入结点,首先获取到此结点的前一个结点,然后在前一个结点后面插入新的结点;但这种插入方式必须首先获取到已知结点的前一个结点,查找过程必须遍历当前结点之前的所有元素才能找到前一个结点;时间复杂度为 O(n),采用另一种方式可以巧妙的将复杂度降低到 O(1);方法为在已知结点的后面插入新的结点,然后交换新节点与已知结点的值,就实现了相同的目的;
void FrontInsert(LNode* node, ElemType e) {
LNode *s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = node->next;
node->next = s;
ElemType temp = node->data;
node->data = s->data;
s->data = temp;
}
2~5 行操作为将新的结点插入到已知结点的后面,6~8 行操作为交换两个结点内的值;
测试:
#include<iostream>
#include<cstdlib>
using namespace std;
typedef int ElemType;
typedef struct LNode {
ElemType data;
struct LNode *next;
} LNode, *LinkList;
void ListInitite(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
}
void Show_List(LinkList L) {
LNode* p = L->next;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
}
void List_HeadInsert(LinkList &L) {
for(int i = 1; i <= 10; i++) { //将 1 ~ 10 按头插法插入单链表
LNode* p = (LNode*)malloc(sizeof(LNode));
p->data = i;
p->next = L->next;
L->next = p;
}
}
int Length(LinkList L) {
LNode* p = L->next;
int length = 0;
while(p) {
length++;
p = p->next;
}
return length;
}
LNode* GetElem(LinkList L, int i) {
if(i == 0) {
return L;
}
if(i < 1 || i > Length(L)) { //若超出链表范围
return NULL;
}
LNode *p = L;
int now = 0;
while(p && now < i) {
p = p->next;
now++;
}
return p;
}
void FrontInsert(LNode* &node, ElemType e) {
LNode *s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = node->next;
node->next = s;
ElemType temp = node->data;
node->data = s->data;
s->data = temp;
}
int main() {
LinkList L;
ListInitite(L);
List_HeadInsert(L);
Show_List(L);
LNode *node = GetElem(L, 5);
FrontInsert(node, 50);
printf("\n");
Show_List(L);
return 0;
}
测试结果:
10 9 8 7 6 5 4 3 2 1
10 9 8 7 50 6 5 4 3 2 1
删除结点操作
删除链表位置为 i 的结点,并将删除的结点存放在 node 中
bool ListDelete(LinkList &L, int i, LNode* &node) {
if(i < 1 || i > Length(L)) {
printf("删除位置错误");
return false;
}
LNode *p = GetElem(L, i - 1);
LNode *q = p->next;
p->next = q->next;
node = q;
free(q);
return true;
}
上述代码有错,free(void* p) 函数的作用是回收 动态分配给 p 的空间,不论有多少指针指向 p 所指向的空间,因此将对于 node = q,在 free(q) 以后 node 所指向的空间也被回收了,因此此处最好不返回结点,返回结点中的值;修正后的代码如下:
bool ListDelete(LinkList &L, int i, ElemType &del) {
if(i < 1 || i > Length(L)) {
printf("删除位置错误");
return false;
}
LNode *p = GetElem(L, i - 1);
LNode *q = p->next;
p->next = q->next;
del = q->data;
free(q);
return true;
}
测试:
#include<iostream>
#include<cstdlib>
using namespace std;
typedef int ElemType;
typedef struct LNode {
ElemType data;
struct LNode *next;
} LNode, *LinkList;
void ListInitite(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
}
void Show_List(LinkList L) {
LNode* p = L->next;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
}
void List_TailInsert(LinkList &L) {
LNode* r = L;
for(int i = 1; i <= 10; i++) {
LNode* p = (LNode*)malloc(sizeof(LNode));
p->data = i;
p->next = r->next;
r->next = p;
r = p;
}
}
int Length(LinkList L) {
LNode* p = L->next;
int length = 0;
while(p) {
length++;
p = p->next;
}
return length;
}
LNode* GetElem(LinkList L, int i) {
if(i == 0) {
return L;
}
if(i < 1 || i > Length(L)) { //若超出链表范围
return NULL;
}
LNode *p = L;
int now = 0;
while(p && now < i) {
p = p->next;
now++;
}
return p;
}
bool ListDelete(LinkList &L, int i, ElemType &del) {
if(i < 1 || i > Length(L)) {
printf("删除位置错误");
return false;
}
LNode *p = GetElem(L, i - 1);
LNode *q = p->next;
p->next = q->next;
del = q->data;
free(q);
return true;
}
int main() {
LinkList L;
ListInitite(L);
List_TailInsert(L);
Show_List(L);
ElemType del;
ListDelete(L, 7, del);
printf("\n");
Show_List(L);
printf("\n删除的元素为:%d", del);
return 0;
}
结果:
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 8 9 10
删除的元素为:7
此处删除的实现依然为后删,即找到将要删除结点的前一个结点进行删除;即给定一个已知结点需要对其进行删除,首先应该找到其前驱节点才能进行删除;和前插法类似,也有减少其复杂度的方法,即首先交换待删除结点后其后继节点的值,然后删除其后继节点;实现方式和前插法类似:
void Del(LinkList &L, LNode* &p) {
LNode* q = p->next;
ElemType temp = q->data;
q->data = p->data;
p->data = temp;
p->next = q->next;
free(q);
}
当然此时对于极端情况,即要删除的元素为最后一个元素时不适用;
测试:
#include<iostream>
#include<cstdlib>
using namespace std;
typedef int ElemType;
typedef struct LNode {
ElemType data;
struct LNode *next;
} LNode, *LinkList;
void ListInitite(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
}
void Show_List(LinkList L) {
LNode* p = L->next;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
}
void List_TailInsert(LinkList &L) {
LNode* r = L;
for(int i = 1; i <= 10; i++) {
LNode* p = (LNode*)malloc(sizeof(LNode));
p->data = i;
p->next = r->next;
r->next = p;
r = p;
}
}
int Length(LinkList L) {
LNode* p = L->next;
int length = 0;
while(p) {
length++;
p = p->next;
}
return length;
}
LNode* GetElem(LinkList L, int i) {
if(i == 0) {
return L;
}
if(i < 1 || i > Length(L)) { //若超出链表范围
return NULL;
}
LNode *p = L;
int now = 0;
while(p && now < i) {
p = p->next;
now++;
}
return p;
}
void Del(LinkList &L, LNode* &p) {
LNode* q = p->next;
ElemType temp = q->data;
q->data = p->data;
p->data = temp;
p->next = q->next;
free(q);
}
int main() {
LinkList L;
ListInitite(L);
List_TailInsert(L);
Show_List(L);
LNode *p = GetElem(L, 4);
Del(L, p);
printf("\n");
Show_List(L);
return 0;
}
结果:
1 2 3 4 5 6 7 8 9 10
1 2 3 5 6 7 8 9 10
单链表的销毁
void Destory(LinkList &L) {
LNode* p = L;
LNode* q = L;
while (q)
{
p = q;
q = q->next;
free(p);
}
free(L);
L=NULL;
}
数据结构 - 单链表 C++ 实现的更多相关文章
- python实现数据结构单链表
#python实现数据结构单链表 # -*- coding: utf-8 -*- class Node(object): """节点""" ...
- C语言数据结构-单链表的实现-初始化、销毁、长度、查找、前驱、后继、插入、删除、显示操作
1.数据结构-单链表的实现-C语言 typedef struct LNode { int data; struct LNode* next; } LNode,*LinkList; //这两者等价.Li ...
- 数据结构——单链表java简易实现
巩固数据结构 单链表java实现 单链表除了表尾 每个几点都有一个后继 结点有数据和后继指针组成 通过构建表头和表尾(尾部追加需要)两个特殊几点 实现单链表的一些操作,代码如下 package co ...
- C# 数据结构--单链表
什么是单链表 这两天看到很多有关单链表的面试题,对单链表都不知道是啥的我.经过学习和整理来分享一下啥是单链表和单链表的一些基本使用方法.最后看些网上有关单链表的面试题代码实例. 啥是单链表? 单链表是 ...
- 数据结构-------单链表(C++)
相关信息: /** * @subject 数据结构 实验2 * @author 信管1142班 201411671210 赖俊杰 * @project 单链表 * @time 2015年10月29日1 ...
- C# 数据结构 - 单链表 双链表 环形链表
链表特点(单链表 双链表) 优点:插入和删除非常快.因为单链表只需要修改Next指向的节点,双链表只需要指向Next和Prev的节点就可以完成插入和删除操作. 缺点:当需要查找某一个节点的时候就需要一 ...
- 数据结构—单链表(类C语言描写叙述)
单链表 1.链接存储方法 链接方式存储的线性表简称为链表(Linked List). 链表的详细存储表示为: ① 用一组随意的存储单元来存放线性表的结点(这组存储单元既能够是连续的.也能够是不连续的) ...
- python算法与数据结构-单链表(38)
一.链表 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成.每个结点包括 ...
- 数据结构——单链表(singly linked list)
/* singlyLinkedList.c */ /* 单链表 */ /* 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素. */ #include <stdio ...
- 数据结构-单链表-类定义2-C++
上一次的C++链表实现两个单链表的连接不太理想,此次听了一些视频课,自己补了个尾插法,很好的实现了两个链表的连接,当然了,我也是刚接触,可能是C++的一些语法还不太清楚,不过硬是花了一些时间尽量在数据 ...
随机推荐
- vue的编译作用域
其实就是在哪个实例中使用vue指令,他所在的作用域就在那个实例中 例如 当组件标签使用vue指令的时候,他所在的作用域就是vue实例对象的作用域,而当组件的 template中 标签使用vue指令的话 ...
- 科普IIS是什么?IIS介绍!
1.Microsoft IIS 是允许在公共Intranet或Internet上发布信息的Web服务器.Internet Information Server通过运用超文本传输协议(HTTP)传输信息 ...
- [SuperSocket2.0]SuperSocket 2.0从入门到懵逼
SuperSocket 2.0从入门到懵逼 SuperSocket 2.0从入门到懵逼 1 使用SuperSocket 2.0在AspNetCore项目中搭建一个Socket服务器 1.1 引入Sup ...
- [LeetCode]1470. 重新排列数组
给你一个数组 nums ,数组中有 2n 个元素,按 [x1,x2,...,xn,y1,y2,...,yn] 的格式排列. 请你将数组按 [x1,y1,x2,y2,...,xn,yn] 格式重新排列, ...
- 云原生 PostgreSQL - CrunchyData PGO 教程:创建、连接、删除 Postgres 集群
入门 作为安装的一部分,请确保您已完成以下操作: 分叉 Postgres Operator 示例存储库并将其克隆到您的主机. https://github.com/CrunchyData/postgr ...
- Web应用程序攻击和检查框架w3af
实验目的 利用w3af爬虫插件探测出目标网站的目录结构. 实验原理 1) W3AF是一个web应用安全的攻击.审计平台,通过增加插件来对功能进行扩展.这是一款用python写的工具,可以查看所有源代码 ...
- 【windows 操作系统】什么是窗口?|按钮也是窗口
起因 在看操作系统消息机制的时候,看到一句化:全局消息队列把消息发送到窗口所在的线程消息队列.突然就怀疑起了窗口的意思.于是就有这边基类. 文章来源:https://docs.microsoft.co ...
- SpringMVC教程--Idea中使用Maven创建SpringMVC项目
1.新建项目 参照idea教程中的创建maven项目https://www.cnblogs.com/daxiang2008/p/9061653.html 2.POM中加入依赖包 (1)指定版本 (2) ...
- JZ-054-字符流中第一个不重复的字符
字符流中第一个不重复的字符 题目描述 请实现一个函数用来找出字符流中第一个只出现一次的字符.例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g&q ...
- 『德不孤』Pytest框架 — 9、Pytest测试报告
目录 1.pytest-html插件 2.Allure测试报告 (1)Allure框架说明 (2)Allure框架的使用 1.pytest-html插件 Pytest可以通过命令行方式,生成xml/h ...