C++ 数据结构第二章 ----- 线性表
文章目录
- 线性表
- 线性表的顺序存储
- 一、基本概念
- 二、基本操作
- 线性表的链式存储
- 一、基本概念
- 二、基本操作
- 三、双链表
- (1) 双链表的插入操作
- (2) 双链表的删除操作
- 四、循环链表
- 五、循环双链表
- 顺序表与链表的比较
线性表
线性表的顺序存储
一、基本概念
- 定义:线性表的顺序存储指的是 用一组地址连续的存储单元 依次存储线性表中的数据元素。
- 注意:
- 在顺序存储中,逻辑上相邻的数据元素,物理上也相邻。
- 顺序表是 随机存取 的存储结构:顺序表中数据元素的存储地址是其序号的线性函数,只要确定了存储顺序表的起始地址,计算任意一个元素的存储地址的时间是相等的。
二、基本操作
const int MaxSize = 100;
template <typename DataType>
class SeqList
{public:SeqList();SeqList(DataType a[], int n);~SeqList();// 将元素x插入第i个位置void Insert(int i, DataType x);// 删除第i个位置的元素DataType Delete(int i);private:// 线性表底层数组DataType data[MaxSize];// 线性表长度int length;
};
插入操作
template <typename DataType> void SeqList<DataType>::Insert(int i, DataType x) {// 判断 i 是否合法if (i <= 0 || i > length + 1) {cout << "输入的位置序号不合法" endl;return;}// 判断一下插入新元素后顺序表是否溢出if (length + 1 > MaxSize) {cout << "上溢" << endl;return;}// 位移数组元素:最后一个空位置等于倒数第一个顺序表中元素,倒数第二个等于倒数第三个 ...for (int j = length; j > i - 1; j--) {data[j] = data[j - 1];}// 修改第 i 个位置的数据data[i - 1] = x; // 长度 + 1length++; }
算法思想:
- 判断插入的位置 i 是否合法
- 判断顺序表的存储空间是否已满
- 将第 n 之第 i 个位置的元素依次往后移动一个位置,空出第 i 个位置的空间
- 将新元素插入到第 i 个位置
- 顺序表长度 + 1
算法时间复杂度分析:
- 最好情况:在表尾部插入数据 (即 i = len + 1),此时为 O(1)
- 最坏情况:在表头插入 (即 i = 1),此时为 O(n)
注意:
- 当插入的位置为第 i 个结点的时候,需要移动
len - i + 1
个元素,len
为插入数据之前数组的长度 - 可以在顺序表的表尾插入元素,无法在表尾之后的位置插入元素了,因为表尾部之后都是空数据
- 插入数据前的顺序表的
len
可以理解为顺序表最后一个元素的后一个空位置 - 第 i 处的元素对应于数组中下标为 i - 1 的元素
删除操作
template <typename DataType> DataType SeqList<DataType>::Delete(int i) { DataType x;// 判断一下删除的位置是否合法if (i <= 0 || i >= length) {cout << "你输入的位置不合法" << endl;return x;}// 拷贝一下数据x = data[i - 1];// 从第 i 到 len - 2 的每一个位置的后一个元素覆盖前面的元素for (int n = i - 1; n < length - 1; n++) {data[n] = data[n + 1];}// 长度 - 1length--;// 返回删除的元素return x; }
算法思想:
- 判断输入的位置 i 是否合法
- 将欲删除的元素保留在 x 中
- 将第 i + 1 至 n 个位置的元素依次向前移动一个位置
- 长度 - 1
- 返回 x
算法时间复杂度分析:
- 最好情况:在顺序表尾部删除元素即 (i = len),此时为 O(1)
- 最坏情况:在表头删除元素即 (i = 1),此时为 O(n)
注意:
- 当删除的位置为第 i 个结点时,需要移动
len - i
个元素
线性表的链式存储
这里来说明一下头结点、首元结点、头指针的概念:
- 头结点:不存放实际的数据,只是为了操作方便而定义的。
- 头指针:指向链表中头结点的指针。
- 首元结点:链表中存储第一个元素数据的结点,其在头结点的后面。
一、基本概念
- 定义:线性表的链式存储指的是 通过一组任意的存储单元(不要求连续) 来存储线性表中的数据,对每个元素除了存储自身信息外,还需要 存放一个指向其后继的指针。
- 注意:
- 线性表的链式存储又称为单链表。
- 逻辑上相邻的元素数据,物理上未必相邻。
- 每个元素和其后继指针统称为单链表结点。
- 单链表结点中有 data 域用于存储元素数据,next 域用于存储下一个结点的内存地址。
- 单链表是一个非随机存取的存储结构:如果想从单链表中取出某个元素,那么必须从链表头部一点点往后面找。
二、基本操作
template <class T>
struct Node
{// 存放元素数据T data;// 存放下一个结点的内存地址Node<T> *next;
};template <class T>
class LinkList
{public:LinkList(); //建立只有头结点的空链表LinkList(T a[], int n, int direction); //建立有n个元素的单链表int Length(); //求单链表的长度T Get(int i); //取单链表中第i个结点的元素值int Locate(T x); //求单链表中值为x的元素序号void Insert(int i, T x); //在单链表中第i个位置插入元素值为x的结点T Delete(int i); //在单链表中删除第i个结点
private:Node<T> *first; //单链表的头指针
};
链表的建立
template <class T> LinkList<T>::LinkList() {// 创建一个头结点,用单链表的头指针指向头结点first = new Node<T>;// 令头结点的后继为 NULLfirst->next = NULL; }template <class T> LinkList<T>::LinkList(T a[], int n, int direction) {// 使用尾插法构造链表if (direction == 1) {// 创建一个头结点,用单链表的头指针指向头结点first = new Node<T>;// 扫描指针指向头结点Node<T> *temp = first;// 遍历数组 afor (int i = 0; i < n; i++) {// 为 a[i] 创建新结点Node<T> *newNode = new Node<T>;newNode->data = a[i];// 扫描指针当前指向的结点的后继指向新结点temp->next = newNode;// 扫描指针指向新结点temp = temp->next;}// 为最后一个新结点的后继赋值为 nulltemp->next = NULL; } else {// 使用尾插法构造链表// 创建一个头结点,用链表的头指针指向头结点first = new Node<T>;for (int i = 0; i < n; i++) {// 为 a[i] 创建一个新结点Node<T> *newNode = new Node<T>;newNode->data = a[i];// 新结点的后继指向头结点后继newNode->next = first->next;// 头结点的后继指向新结点first->next = newNode;}} }
算法思想:
(1) 头插法- 用链表的头指针指向新创建的头结点
- 用一个扫描指针指向头结点
- 遍历数组
- 在遍历过程中,为每一个元素数据创建一个新的结点
- 扫描指针的后继指向新的结点
- 扫描指针指向新的结点
- 遍历完成后,扫描指针指向了最后一个新的结点,令其后继为 NULL
(2) 尾插法
- 用链表的头指针指向新创建的头结点
- 遍历数组
- 在遍历过程中,为每一个元素数据创建一个新的结点
- 让新结点的后继指向头指针的后继
- 让头结点的后继指向新结点
注意:
- 尾插法建立单链表的结点顺序和传入的数组的顺序一致,因为尾插法相当于往链表尾部 append 一个个数据
- 头插法建立单链表的结点顺序和传入的数组的顺序相反,因为每一次从数组中取出来的数据都放在了头结点后面的一个新插入的结点中,那么数组中最后一个元素一定是在首元结点中的
求单链表长度
template<class T> int LinkList<T>::Length() {int cnt = 0;// 扫描指针指向头结点Node<T> *temp = first;while (temp->next != NULL) {cnt++;temp = temp->next;}return cnt; }
算法思想:
- 定义一个变量 cnt 用来计数结点个数
- 用一个扫描指针指向头结点
- 使用一个 while 循环,只要扫描指针所指向的结点的后继不为空则进行循环
- while 循环内部让 cnt 自增 1 ,使扫描指针指向其后继结点
- 循环结束后返回 cnt
注意:
- 该思想可以用作遍历单链表的思想
按序号查找结点值
template<class T> T LinkList<T>::Get(int i) {int order = 0;Node<T> *temp = first;while (temp->next != NULL) {order++;temp = temp->next;if (order == i) {return temp->data;}}throw "未查找到"; }
按结点值查找序号
template<class T> int LinkList<T>::Locate(T x) {int order = 0;Node<T> *temp = first;while (temp->next != NULL) {order++;temp = temp->next;if (temp->data == x) {return order;}}throw "未查找到"; }
插入新结点操作
template <class T> void LinkList<T>::Insert(int i, T x) {// 记录 temp 指针的后一个位置int backOrder = 0;// 扫描指针指向头结点用于遍历链表Node<T> *temp = first;while (temp->next != NULL) {// 扫描指针后移backOrder++;// 如果当前位置与输入的位置相同则进行数据插入if (backOrder == i) {// 为 x 数据创建一个结点Node<T> *newNode = new Node<T>;newNode->data = x;// 新结点的后继指向扫描指针的后继newNode->next = temp->next;// 扫描指针指向的当前结点(新结点的前一个结点)的后继指向新结点temp->next = newNode;}temp = temp->next;}}
算法思想:
- 定义一个变量 backOrder 记录扫描指针所在的后一个位置
- 定义一个扫描指针指向头结点
- 如果扫描指针的后继不是空则进入 while 循环,backOrder 自增 1
- 如果 backOrder 等于了输入的位置 i ,则为 x 创建一个新结点
- 令新结点的后继指向扫描指针的后继,扫描指针的后继指向新结点
- 如果 backOrder 没有匹配上 i ,则继续往链表后面扫描
注意:
- 这里的 backOrder 记录的是扫描指针的后一个位置
删除结点操作
template<class T> T LinkList<T>::Delete(int i) {// 记录扫描指针当前所在位置int order = 0;// 扫描指针Node<T> *temp = first->next;// 前扫描指针Node<T> *preTemp = first;while (temp != NULL) {order++; if (order == i) {// 拷贝一下当前扫描指针指向的结点Node<T> *oldNode = temp;// 拷贝数据T x = oldNode->data;// 扫描前指针指向的结点的后继指向扫描指针的后继preTemp->next = temp->next;// 释放内存delete oldNode;return x;}preTemp = temp;temp = temp->next;}throw "输入的序号不合法"; }
算法思想:
- 创建一个 order 变量用来记录扫描指针当前所在位置
- 扫描指针指向头指针的后继
- 创建一个前扫描指针,其始终在扫描指针的前一个位置
- 扫描指针扫描单链表,如果其所在位置匹配了 i,则令扫描前指针的后继等于扫描指针的后继,拷贝扫描指针当前结点和结点数据,释放结点内存,返回结点存放的元素数据
- 如果没有匹配,则扫描指针和前扫描指针继续往后扫描
注意:
- 前扫描指针 preTemp 始终在扫描指针的前面一个位置
三、双链表
双链表是单链表的改进,其结点新增加了一个 prior
域,保存其前驱内地地址。
template <class T>
struct DNode
{// 存放元素数据T data;// 存放上一个结点的内存地址DNode<T> *prior;// 存放下一个结点的内存地址DNode<T> *next;
};template <class T>
class DLinkList
{public:DLinkList(); //建立只有头结点的空链表DLinkList(T a[], int n, int direction); //建立有n个元素的单链表~DLinkList(); //析构函数void Insert(int i, T x); //在单链表中第i个位置插入元素值为x的结点T Delete(int i); //在单链表中删除第i个结点
private:DNode<T> *first; //单链表的头指针
};
注意:
- 双链表中 按值查找 和 按位查找 的操作和单链表相同。
- 双链表中 插入 和 删除 操作与单链表有很大的不同。
(1) 双链表的插入操作
template <class T>
void DLinkList<T>::Insert(int i, T x)
{int order = 0;DNode<T> *temp = first;while (temp->next != NULL){order++;temp = temp->next;if (order == i){// 创建一个新结点DNode<T> *newNode = new DNode<T>;newNode->data = x;// 扫描指针的前一个结点的后继指向新结点temp->prior->next = newNode;// 新结点的前驱指向扫描指针前一个结点newNode->prior = temp->prior;// 扫描指针的前驱指向新结点temp->prior = newNode;// 新结点的后继指向扫描指针newNode->next = temp;}}
}
算法思想:
- 当 order 与 i 匹配时,扫描指针 temp 处于插入位置两个结点中的后一个结点
- 先让前一个结点的后继指向新结点,然后让新结点的前驱指向前一个结点
- 再让扫描指针所在结点的前驱指向新结点,然后让新结点的后继指向扫描指针
注意:
- 如果扫描指针处于插入位置两个结点的前一个结点
- 先让后一个结点的前驱指向新结点,然后让新结点的后继指向后一个结点
- 再让扫描指针所在结点的后继指向新结点,然后让新结点的前驱指向扫描指针所在结点
(2) 双链表的删除操作
template <class T>
T DLinkList<T>::Delete(int i) {int order = 0;DNode<T> *temp = first;while (temp->next != NULL) {order++;temp = temp->next;if (order == i) {// 扫描指针前一个结点的后继指向扫描指针的后一个结点temp->prior->next = temp->next;// 扫描指针后一个结点的前驱指向扫描指针前一个结点temp->next->prior = temp->prior;}}
}
四、循环链表
- 定义:单链表的最后一个结点的 next 域指向头结点的链表
- 注意:
- 循环单链表中没有 next 域为 NULL 的结点,所以判空的方法和单链表不同,一个空的循环单链表,其头结点的 next 域指向的是其本身,只需要判断
temp->next == temp
即可 - 循环单链表的插入和删除操作基本上和单链表的操作一致,需要注意的就是表尾处的最后一个结点需要指向其表头
- 循环单链表中没有 next 域为 NULL 的结点,所以判空的方法和单链表不同,一个空的循环单链表,其头结点的 next 域指向的是其本身,只需要判断
五、循环双链表
- 定义:循环双链表中,将其最后一个结点的 next 域指向了头结点,头结点的 prior 域指向了其最后一个结点
- 注意:
- 循环双链表中也没有为空的 next 域和 prior 域,所以只需要判断
temp->next == temp
即可 - 循环双链表的插入和删除操作基本上和双链表的操作一致,需要注意的就是要保存循环的特性
- 循环双链表中也没有为空的 next 域和 prior 域,所以只需要判断
顺序表与链表的比较
顺序表 | 链表 | |
---|---|---|
存取方式 | 顺序存取、随机存取 | 只能顺序存取 |
存储结构 | 逻辑上相邻,物理上也相邻 | 逻辑上相邻、物理上不一定相邻 |
插入、删除操作 | 需要移动大量元素 | 只需要修改相关结点的指针 |
空间分配 | 静态分配时需要预分配足够大的空间,动态分配时可能会移动大量元素 | 在需要时申请即可,并且可以扩充 |
存储密度 | 存储密度大 | 存储密度小 |
C++ 数据结构第二章 ----- 线性表相关推荐
- 数据结构第二章-线性表(详细知识点总结)
目录 第二章 线性表 2.1 线性表的定义和操作 2.1.1 线性表的定义 2.1.2 线性表的基本操作 2.2线性表的顺序表示 2.2.1 顺序表的定义 2.2.2 顺序表上基本操作的实现 2.3 ...
- C语言数据结构-第二章线性表-电大
第二章线性表--内容简介 本章将进入线性结构的学习. 线性结构是最简单.最常用的一种数据结构. 本章将学习线性表的定义.顺序和链式两种存储方式及相应存储结构上的运算实现.通过典型示例训练,掌握线性表的 ...
- 王道408数据结构——第二章 线性表
文章目录 一.线性表的定义和基本操作 线性表 顺序表 1.插入操作 2.删除操作 3.按值查找(顺序查找) 二.单链表 1. 头插法 2. 尾插法 3. 按序号查找 4. 按值查找 5. 插入结点 6 ...
- 数据结构第二章线性表学习笔记
1. C++程序设计模板 (关于template的解释)[以下内容摘自百度] 函数声明格式 template <class(或typename) any(或任意符合规则的名称)> ...
- (王道408考研数据结构)第二章线性表-第三节5:顺序表和链表的比较
文章目录 一:逻辑结构比较 二:存储结构比较 三:基本操作比较 (1)初始化操作 (2)销毁操作 (3)插入和删除 (4)查找 顺序表和链表的选取原则 一:逻辑结构比较 顺序表和链表都是线性表,都是线 ...
- (王道408考研数据结构)第二章线性表-第三节1:单链表的定义及其操作(插入和删除,建立之尾插和头插)
文章目录 一:单链表相关 (1)单链表的定义 (2)头指针与头结点 二:单链表代码描述 三:单链表的初始化 四:单链表的插入 五:单链表的删除 六:单链表查找 (1)按位查找 (2)按值查找 七:单链 ...
- (王道408考研数据结构)第二章线性表-第二节1:顺序表的定义
文章目录 一:顺序表实现 (1)静态分配 (2)动态分配 二:顺序表特点 顺序表:也叫做线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素 一:顺序表实现 (1)静态分配 静 ...
- (王道408考研数据结构)第二章线性表-第一节:线性表的定义和基本操作
文章目录 一:线性表的定义 二:线性表的基本操作 一:线性表的定义 线性表(Linear List):零个或多个数据元素的有限序列 元素之间是有顺序的 若元素存在多个,则第一个元素无前驱,最后一个元素 ...
- PTA数据结构第二章线性表
顺序表 1-8 对于顺序存储的长度为N的线性表,访问结点和增加结点的时间复杂度分别对应为O(1)和O(N). T F 1-10 对于顺序存储的长度为N的线性表,删除第一个元素和插入最后一个元素的时间复 ...
最新文章
- mysql+esc,mysql(3):基础,常用命令句使用(2)--上集
- python--采集1(urllib模块)
- 使用Envoy 作Sidecar Proxy的微服务模式-4.Prometheus的指标收集
- 销售系统软件mysql_Max(TM)销售管理系统
- Oracle_spatial的空间索引
- 微软智能云Azure Kubernetes容器服务落地中国,开启预览
- Java prepare
- k3刷梅林5g信号不稳定_斐讯k3 5g信号不稳定 k3c路由器,现在还能入手吗?
- 地理加权回归R语言实例
- 达梦数据库DSC小记
- C++中deprecated笔记
- 钉钉打卡作弊软件非法获利近 500 万元,CEO 被判刑 5 年 6 个月
- HDU 1867(kmp应用)
- 严正声明:不要抄袭本人的创作内容
- 【舒利迭】 沙美特罗替卡松粉吸入剂 (50微克 250微克)
- 我们要不要和to B“霸王龙”企业交朋友?
- CPU问题导致的大量进程崩溃问题
- ac3音频 机顶盒播放音量变小问题
- libusb常用函数说明
- 坦克世界服务器系统不更新失败怎么办,坦克世界安装更新数据失败怎么办? 爱问知识人...
热门文章
- java.lang.NullPointerException org.apache.jsp.index_jsp._jspInit(index_jsp.java:22)
- Springmvc+mybaits 分页处理+ajax翻页
- Windows API 学习记录1
- Ubuntu 10.10安装Vmware Tools
- IIS故障:World Wide Web Publishing无法启动 提示1721错误
- 诗歌rails之Hacking ActiveRecord
- apache tomcat php mysql 配置_Apache与Tomcat服务器整合的基本配置方法及概要说明
- rez注入器源码_CF-rez-Tool crossfire的REZ文件全套修改工具 - 下载 - 搜珍网
- 读书笔记--云边有个小卖部
- springboot后台怎么获取前端传过来的excel_基于SpringBoot的全自动办公系统源码文档全部免费分享...