本文例程下载链接:ListDemo

链表 vs 数组

链表和数组的最大区别在于链表不支持随机访问,不能像数组那样对任意一个(索引)位置的元素进行访问,而需要从头节点开始,一个一个往后访问直到查找到目标位置。

单链表

与顺序表相对,链表是一种链式存储方式。单链表是实现顺序表最简单的一种链表,根据是否包含虚拟头节点,分为含虚拟头节点和不含虚拟头节点两种方式。本文以含虚拟头节点为例,用C++实现单链表数据结构ADT。

节点:链表的组成元素,每个节点包含数据域data和指针域next。例程为了简化设计,data用int类型表示。

虚拟头节点:链表的第一个节点,为了维护链表操作方便,但不存放数据。

虚拟头节点(dummy head) VS 无虚拟头节点

含虚拟头节点的链表优势:所有的数据节点(除去头节点)都是对等的,对链表节点的API操作一般不影响头节点本身指针变化(除去头节点next域)。

缺点:需要额外考虑头节点影响,比如IsEmpty()判断条件, 和Length()是否对等(IsEmpty <=> length==0?),头节点对用户是否可见,能否被用户直接remove等。

单链表结构设计

含虚拟头节点的单链表list如下图所示,包含一个虚拟头节点V0,而V1~Vn通过前驱节点next域进行链接,从而形成一个单向链式结构。

虚拟头节点V0,不包含数据;V1~Vn包含数据,Vn->next = NULL

名称 描述 代表自定义符号
虚拟头节点 不包含数据 Vh/head
尾节点 链表的最后一个节点,特征:next=NULL Vn-1/tail
普通数据节点 包含具有实际意义数据的节点 V0~Vn-1
长度 包含实际意义数据节点数为n(不包括Vh) length
空链表 虚拟头节点为空,即head=NULL V0=NULL
位置

从V0开始(Vh后继节点)开始计数0,直到Vn-1(尾节点)的节点对应位置。范围[0,n-1]

position
插入节点 在指定位置处插入节点,除插入节点及前驱和后继,不改变链表其他节点关系 insert
删除节点 在指定位置处删除节点, 除插入节点及前驱和后继,不改变链表其他节点关系 remove
     

单链表ADT设计

1. 链表节点ADT Node.h

/**
* 单链表节点类
*/
class Node
{
int data;
Node *next; public:
Node();
Node(Node*, int);
};

2. 单链表ADT LinkedList.h

/**
* 单链表类
* @description 带虚拟头节点
*/
class LinkedList
{
private:
Node *head;
Node *tail; public:
LinkedList();
virtual ~LinkedList();
Status Init(); // 初始化链表
Status Destroy(); // 销毁链表 int Length(); // 求链表长度(有效节点)节点个数
bool IsEmpty(); // 链表是否为空
Status Insert(int pos, int value); // 在指定位置生产节点, 并插入数据
Status Remove(int pos); // 删除指定位置节点
int GetValue(int pos); // 读取指定位置节点数据
Node* GetAddress(int pos); // 获取知道位置节点的地址
int SearchPosition(int value); // 搜索第一个出现的元素值的节点位置
Status Update(int pos, int value); // 更新指定位置节点数据
Status ClearList(); // 清除链表数据(不包含头节点)
Status PrintList(); // 顺序打印链表节点数据
Status Reverse(); // 反转链表
};

3. Node实现 Node.cpp

#include "Node.h"
#include <cstdlib> Node::Node() {
data = 0;
next = NULL;
} Node::Node(Node* newNext, int newValue)
{
data = newValue;
next = newNext;
} Node::~Node()
{
// TODO Auto-generated destructor stub
}

4. 链表实现LinkedList.cpp

#include "LinkedList.h"
#include <cstdlib>
#include <iostream> using namespace std; LinkedList::LinkedList() {
head = NULL;
tail = NULL;
} LinkedList::~LinkedList() {
Destroy();
} /**
* 初始化链表
*/
Status LinkedList::Init()
{
// 创建虚拟头节点
head = new Node(NULL, 0); if(head != NULL)
{
return OK;
} return ERROR;
} /**
* 销毁链表
* @description 与初始化操作相对, 删除所有链表节点, 包括头节点
*/
Status LinkedList::Destroy()
{
if(head)
{
Node *curP = head; while(head)
{
curP = head->next;
delete head;
head = curP;
} return OK;
}
else
{
cout <<"The list is NULL"<<endl;
return ERROR;
}
} /**
* 求链表长度(有效节点)节点个数
* @description 从虚拟头节点的下一个节点开始计算有效节点数
*/
int LinkedList::Length()
{
if(!head)
{
cout <<"The list is NULL"<<endl;
exit(-1);
} Node *curP = head->next;
int n = 0;
while(curP != NULL)
{
n ++;
curP = curP->next;
} return n;
} /**
* 判断链表是否为空
* @description 判断依据: 虚拟头节点head == NULL
* 注: 长度为0 不代表链表为空
*/
bool LinkedList::IsEmpty()
{
return (head == NULL);
} /**
* 在指定位置生产节点, 并插入数据
* @param pos [in] 待插入位置. 从头节点的后继开始为0计数,所需要经过的节点数。范围:0~n-1
* @param value [in] 待插入节点数据域
*/
Status LinkedList::Insert(int pos, int value)
{
if(IsEmpty())
{
cout <<"The list is NULL. Pls Create an List and init it first."<<endl;
exit(-1);
} // create a new Node
Node *newNode = new Node(NULL, value); if(pos == 0)
{
newNode->next = head->next;
head->next = newNode;
}
else if(pos >0 && pos <= Length())
{
// find the predecessor node to be inserted
Node *p = GetAddress(pos-1); // insert the new Node to List
if(p != NULL)
{
if(p->next != NULL)
{// not tail
newNode->next = p->next;
}
p->next = newNode;
}
}
else
{
cout<<"Error: Input param Pos is illegal(<0)"<<endl;
return ERROR; } return OK;
} /**
* 删除指定位置节点
* @param pos [in] 待插入位置. pos有效范围: [0, n), 0代表虚拟节点后面一个节点, 虚拟节点无法通过此API删除
*
*/
Status LinkedList::Remove(int pos)
{
Node *preNode = NULL;
Node *curNode = NULL;
Node *nextNode = NULL; if(pos < 0 || pos >= Length())
{
cout << "Remove node with error position"<<endl;
exit (-1);
}
else if(pos == 0)
{// find the prior node, namely , head node
preNode = head;
}
else if(pos < this->Length())
{
// find the prior node
preNode = GetAddress(pos - 1);
} if(!preNode)
{
return ERROR;
} curNode = preNode->next;
if(curNode)
{
nextNode = curNode->next;
preNode->next = nextNode;
delete curNode;
} return OK;
} /**
* 读取指定位置节点数据
*/
int LinkedList::GetValue(int pos)
{
// find the node
Node *curP = GetAddress(pos);
if(curP != NULL)
{
return curP->data;
}
else
{
return 0;
}
} /**
* 获取位置节点的地址
* @param pos [in] 从头节点的后继开始为0计数,所需要经过的节点数。范围:[0, n-1], n是链表长度(有效数据节点数)
* @return 节点的地址
*/
Node* LinkedList::GetAddress(int pos)
{
// valid list is null or not
if(!head)
{
cout <<"The list is NULL"<<endl;
exit(-1);
} // valid intput param
if(pos < 0 || pos >= Length())
{
cout <<"Insert position is out of the list's bounds"<<endl;
exit(-1);
} // 顺序查找位置pos的节点
int i = 0;
Node *curP = head->next;
while(curP != NULL)
{
if(i == pos)
{
return curP;
} i ++; curP = curP->next;
} return NULL;
} /**
* 搜索第一个出现的元素值的节点位置
* @param value 待查找值
* @return 链表第一个节点的数据域=value的位置
* - ERROR 错误
* - >=0 位置序号
*/
int LinkedList::SearchPosition(int value)
{
// valid list is null or not
if(!head)
{
cout <<"The list is NULL"<<endl;
exit(-1);
} // sequential search
Node *p = head->next;
int i = 0;
while(p != NULL)
{
i ++;
if(p->data == value)
return i;
else
p = p->next;
} cout<< "Can't find the value in list"<<endl;
return ERROR;
} /**
* 更新指定位置节点数据
* @param pos [in] 待更新节点位置
* @param value [in] 待更新节点要修改的目标值
* @return 更新结果
* - OK 正常更新
* - ERROR 没有找到待更新节点, 可能由位置错误或者链表为空导致
*/
Status LinkedList::Update(int pos, int value)
{
// find the node
Node *curP = GetAddress(pos);
if(curP != NULL)
{
curP->data = value;
return OK;
} return ERROR;
} /**
* 清除链表数据(不包含头节点)
*/
Status LinkedList::ClearList()
{
// valid list is null or not
if(!head)
{
cout <<"The list is NULL"<<endl;
exit(-1);
} Node *p = head->next;
Node *tmp = NULL; while(p!=NULL)
{
tmp = p;
p = p->next;
delete tmp;
} head->next = NULL; return OK;
} /**
* 顺序打印链表节点数据
*/
Status LinkedList::PrintList()
{
// valid list is null or not
if(IsEmpty())
{
cout <<"The list is NULL. Pls Create an List and init it first."<<endl;
return ERROR;
} cout <<"List:[ ";
if(Length() == 0)
{
cout<<"NULL";
}
else
{
Node *p = head->next; while(p != NULL)
{
cout<<p->data<<" ";
p = p->next;
}
}
cout <<"] "<<endl;
return OK;
} /**
* 反转链表, 除虚拟头节点
*/
Status LinkedList::ReverseList()
{
// valid list is null or not
if(IsEmpty())
{
cout <<"The list is NULL. Pls Create an List and init it first."<<endl;
exit(-1);
} if(Length() == 0)
{
cout <<"The list length = 0. Pls insert new Node."<<endl;
exit(-1);
} // reverse List
Node *pre = head->next; // precursor Node
Node *cur = pre->next; // current Node
Node *nxt = NULL; // successor Node while(cur != NULL)
{
nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
} // set the tail Node
head->next->next = NULL; // set the head Node's next field
head->next = pre; return OK;
}

 

6. 测试结果

可以看到已经实现了链表基本的插入、打印、反转等功能。

单链表 C++ 实现 - 含虚拟头节点的更多相关文章

  1. 以K个为一组反转单链表,最后不足K个节点的部分也反转

    package StackMin.ReverseList_offer16; public class ReverseKgroup_extend_offer16 { /** * 分组反转单链表,最后不足 ...

  2. 20140719 找到单链表的倒数第K个节点 判断一个链表是否成为一个环形 反转

    1.找到单链表的倒数第K个节点 2.判断一个单链表对否形成环形 3.单链表翻转

  3. C语言:将带头节点的单向链表结点域中的数据从小到大排序。-求出单向链表结点(不包括头节点)数据域中的最大值。-将M*N的二维数组中的数据,按行依次放入一维数组,

    //函数fun功能是将带头节点的单向链表结点域中的数据从小到大排序. //相当于数组的冒泡排序. #include <stdio.h> #include <stdlib.h> ...

  4. Leetcode24--->Swap Nodes in Pairs(交换单链表中相邻的两个节点)

    题目:给定一个单链表,交换两个相邻的节点,且返回交换之后的头节点 举例: Given 1->2->3->4, you should return the list as 2-> ...

  5. C语言实现单链表(不带头结点)节点的插入

    对单链表进行增删改查是最主要的操作.我在上一篇博客<C语言实现链表节点的删除>实现了删除单链表中的某个节点. 这里我们要来实如今某个位置插入节点.演示样例代码上传至https://gith ...

  6. iOS常用算法之单链表查找倒数第n个节点(图解)

    拿到题目, 首先要先了解链表数据结构, 如下图: 常规思路: 利用数组, 遍历整个单链表, 将每个节点装入数组中, 最终拿到数组根据索引(数组长度-1-n)就得到了倒数第n个元素, 这里要注意从数组中 ...

  7. [LeetCode题解]86. 分隔链表 | 三指针 + 虚拟头节点

    解题思路 三指针,一个指向前半部分待插入位置,一个指向后半部分待插入位置,最后一个从前往后遍历 代码 /** * Definition for singly-linked list. * public ...

  8. 面试题-----求单链表的倒数第k个节点

    #include <iostream> using namespace std; struct node{ int value; struct node *next; }; struct ...

  9. C++获取单链表的倒数第k个节点

    /* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } };*/ c ...

随机推荐

  1. 深度学习之tensorflow框架(下)

    def tensor_demo(): """ 张量的演示 :return: """ tensor1 = tf.constant(4.0) t ...

  2. InkWell容器 和 官方自带日期组件 和第三方 日期格式转换组件

    带点击事件的容器 InkWell( child: Text('时间'), onTap: _showTimePicker,),   Flutter 日期和时间戳 日期转化成时间戳: var now = ...

  3. RAID 5+备份硬盘实验:mdadm

    *独立冗余磁盘阵列---RAID5* RAID5+备份盘: 把硬盘设备的数据奇偶校验信息保存到其他硬盘设备中.  RAID 5磁盘阵列组中数据的奇偶校验信息并不是单独保存到某一块硬盘设备中, 而是存储 ...

  4. 【ES6新增语法详述】

    目录 1. 变量的定义 let const 2. 模版字符串 3. 数据解构 4. 函数扩展 设置默认值 箭头函数 5. 类的定义 class 6. 对象的单体模式 "@ ES6新增了关于变 ...

  5. 解密国内BAT等大厂前端技术体系-美团点评之下篇(长文建议收藏)

    引言 在上篇中,我已经介绍了美团点评的业务情况.大前端的技术体系,其中大前端的技术全景图如下: 上篇重点介绍了工程化和代码质量的部分,工程化涵盖了客户端持续集成平台-MCI.全端监控平台-CAT.移动 ...

  6. PAT 1013 Battle Over Cities (dfs求连通分量)

    It is vitally important to have all the cities connected by highways in a war. If a city is occupied ...

  7. Windows上面搭建FlutterAndroid运行环境

    1.下载安装JDK https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 2.配置J ...

  8. 使用Log4net记录日志(非常重要)

    使用Log4net记录日志   首先说说为什么要进行日志记录.在一个完整的程序系统里面,日志系统是一个非常重要的功能组成部分.它可以记录下系统所产生的所有行为,并按照某种规范表达出来.我们可以使用日志 ...

  9. vue-webpack模板升级到webpack4

    本文仅简单记录下基于vue-webpack模板升级到webpack4的过程 快速部署 Vue CLI 的包名称由 vue-cli 改成了 @vue/cli # 全局安装 vue-cli $ npm i ...

  10. 【MySQL】库的操作

    "SQL语言主要用于存取数据.查询数据.更新数据和管理关系数据库系统,SQL语言由IBM开发. SQL语言分为3种类型: DDL语句 数据库定义语言:数据库.表.视图.索引.存储过程,例如C ...