• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
学习?学个屁!
博客园    首页    新随笔    联系   管理    订阅  订阅
数据结构与算法基础笔记CPP代码(排序未写完)

基本概念和术语

  • 数据

  • 是能输入计算机且能被计算机处理的各种符号的集合

    • 信息的载体
    • 是对客观事物符号化的表示
    • 能够被计算机识别、存储和加工

    包括数值数据和非数值数据

  • 数据元素是组成数据的基本单位

    • 也简称为元素,或称为记录、结点或顶点
  • 数据项是构成数据元素的不可分割的最小单位

  • 数据对象是性质相同的数据元素的集合


  • 数据结构

    • 数据元素之间的关系就是结构

    • 数据结构就是指相互之间存在一种或多种特定关系的数据元素集合

    • 数据结构包括以下三个方面的内容

      1. 数据元素之间的逻辑关系,也称为逻辑结构
      2. 数据元素及其关系在计算机内存中的表示(又称为映像),称为数据的物理结构或数据的存储结构
      3. 数据的运算和实现,即对数据元素可以施加的操作以及这些操作在相应的存储结构上的实现
    • 逻辑结构
      是描述数据元素之间的逻辑关系,与数据的存储无关,独立于计算机,是从具体问题抽象出来的数学模型

    • 物理结构 (存储结构)
      数据元素及其关系在计算机存储器中的结构(存储方式)
      是数据结构在计算机中的表示


逻辑结构是数据结构的抽象,存储结构是数据结构的实现

四种基本逻辑结构:

  1. 集合结构:结构中的数据元素之间除了同属于一个集合的关系外,无任何其它关系

  2. 线性结构:结构中的数据元素之间存在着一对一的线性关系

  3. 树形结构:结构中的数据元素之间存在着一对多的层次关系

  4. 图状结构或网状结构:结构中的数据元素之间存在着多对多的任意关系

四种基本存储结构:

  1. 顺序存储结构
    用一组连续的存储单元依次存储数据元素,数据元素之间的逻辑关系由元素的存储位置来表示
    C语言中用数组来实现顺序存储结构

  2. 链式存储结构
    用一组任意的存储单元存储数据元素,数据元素之间的逻辑关系用指针来表示
    C语言中用指针来实现链式存储结构

  3. 索引存储结构

    • 在存储结点信息的同时,还建立附加的索引表

    • 索引表中每一项就是一个索引项

    • 索引项的一般形式是:(关键字,地址)

    • 关键字是能唯一标识一个结点的那些数据项

    • 若每个结点在索引表中都有一个索引项,则该索引表称之为稠密索引(Dense Index),若一组结点在索引表中只对应一个索引项,则该索引表称之为稀疏索引(Sparse Index)

      比如手机通讯录列表

  4. 散列存储结构

    根据节点的关键字直接计算该节点的存储位置

数据类型和抽象数据类型

一些最基本数据结构可以用数据类型来实现,如数组、字符串等;而另一些常用的数据结构,如栈、队列、树、图等,不能直接用数据类型来表示

  • 数据类型的作用
    1. 约束变量或常量的取值范围
    2. 约束变量或常量的操作

数据类型(Data Type)
定义:数据类型是一组性质相同的值的集合以及定义于这个值集合上的一组操作的总称
数据类型=值的集合+值集合上的一组操作

抽象数据类型(Abstract Data Type)是指一个数学模型以及定义在此数学模型上的一组操作

  • 由用户定义,从问题抽象出数据模型(逻辑结构)
  • 还包括定义在数据模型上的一组抽象运算(相关操作)
  • 不考虑计算机内的具体存储结构与运算的具体实现算法

一个抽象数据类型的定义格式如下:

ADT 抽象数据类型名
    数据对象 <数据对象的定义>
    数据关系 <数据关系的定义>
    基本操作 <基本操作的定义>
ADT 抽象数据类型名

数据对象、数据关系的定义用伪代码描述

基本操作的定义格式为:

基本操作名(参数表)
//赋值参数只为操作提供输入值
//引用参数除可提供输入值外,还将返回操作结果
初始条件: (初始条件描述)
//描述操作执行之前数据结构和参数应满足的条件
//若不满足初始条件,则操作失败,并返回相应出错信息
//若初始条件为空,则省略
操作结果:(操作结果描述)
//说明操作正常完成之后,数据结构的变化状况和应返回的结果

算法

算法的定义

  • 对特定问题求解方法和步骤的一种描述,它是指令的有限序列。其中每个指令表示一个或多个操作
  • 简单说算法就是解决问题的方法和步骤

可以使用自然语言、流程图、伪代码和程序代码来描述算法

算法与程序

  • 算法是解决问题的一种方法或一个过程,考虑如何将输入转换成输出
    一个问题可以有多种算法
  • 程序是用某种程序设计语言对算法的具体实现
  • 程序=数据结构+ 算法
    • 数据结构通过算法实现操作
    • 算法根据数据结构设计程序

算法特性

  • 有穷性
    一个算法必须总是在执行有穷步之后结束,且每一步都在有穷时间内完成。
  • 确定性
    算法中的每一条指令必须有确切的含义,没有二义性
    在任何条件下,只有唯一的一条执行路径,即对于相同的输入只能得到相同的输出。
  • 可行性
    算法是可执行的,算法描述的操作可以通过已经实现的基本操作执行有限次来实现.
  • 输入
    一个算法有零个或多个输入
  • 输出
    一个算法有一个或多个输出

算法设计要求

  • 正确性(Correctness)

    指算法满足问题要求,能够正确解决问题

    算法转化为程序要注意:

    1. 算法程序没有语法错误
    2. 算法程序对于合法的输出数据能够产生满足要求的输出结果
    3. 算法程序对于非法的输入数据能够得出满足规格说明的结果
    4. 算法程序对于精心选择的,甚至刁难的测试数据都有满足要求的输出结果

    通常以第三层意义上的正确性作为衡量一个算法是否合格的标准

  • 可读性(Readability)

    1. 算法设计的主要目的是为了便于阅读、理解和交流,其次才是为计算机执行
    2. 可读性高有助于理解算法,晦涩难懂的算法往往隐含错误,不易被发现且难以调试和修改
  • 健壮性(Robustness)

    1. 当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果
    2. 处理出错的方法,不应是中断程序的执行,而应是返回一个表示错误或错误性质的值,以便在更高的抽象层次上进行处理
  • 高效性(Efficiency)

    时间效率尽量高,存储需求尽量少

    1. 时间效率是算法的执行时间
    2. 存储量需求是算法在执行过程中需要的最大存储空间

算法分析

在满足算法设计要求的前提下考虑算法的效率

算法效率通过两个方面考虑:

  1. 时间效率:算法所耗费的执行时间

  2. 空间效率:算法在执行过程中耗费的存储空间

算法时间效率的度量

  • 算法时间效率可以用依据该算法编制的程序在计算机上执行所消耗的时间来度量

    • 两种度量方法

      • 事后统计

        • 将算法实现,测算其时间和空间开销
        • 缺点:编写程序实现算法将花费较多的时间和精力;所得实验结果依赖于计算机的软硬件等环境因素,掩盖算法本身的优劣
      • 事前分析
        对算法所消耗资源的一种估算方法

算法运行时间=一个简单操作所需时间*简单操作次数

也就是算法中每条语句的执行时间之和
算法运行时间=每条语句的执行次数*该语句执行一次所需的时间

语句的执行次数也称为语句频度
算法运行时间=每条语句频度*该语句执行一次所需的时间

每条语句执行一次所需的时间,取决于不同机器的指令性能、速度以及编译的代码质量。是由机器本身软硬件环境决定的
所以,我们可假设执行每条语句所需的时间均为单位时间
此时对算法运行时间的讨论就可转化为讨论该算法中所有语句的执行次数,即频度之和
这样就可以独立于不同机器的软硬件环境来分析算法的时间性能

例如两个n*n矩阵相乘的算法:

for (i = 1; i <= n; i++)//n+1次,判断是否满足条件也是一次执行
	for (j = 1; j <= n; j++) {//n(n+1)次
   		 c[i][j] = 0;//n*n次
   		//外层循环执行1次,内层循环就会执行n次
    	for (k = 0; k < n; k++)				//n*n*(n+1)次
        c[i][j] = c[i][j] + a[i][K] * b[K][j];//n*n*n次   	
}

算法所耗费的时间为该算法中每条语句的频度之和
则上述算法所耗费的时间T(n)为:
$$
T(n)=2n3+3n2+2n+1
$$
为了便于比较不同算法的时间效率,我们仅比较它们的数量级
例如:两个不同的算法,时间消耗分别是:
T1(n)=10N2 与 T2(n)=5n3
后者数量级比前者大,则算法时间效率不如前者

  • 若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(n)为算法的渐进时间复杂度(O是数量级的符号),简称时间复杂度

上述的算法渐进时间复杂度是T(n)=O(n3)
一般情况下,不必计算所有操作的执行次数,而只考虑算法中基本操作执行的次数,它是问题规模n的某个函数,用T(n)表示

算法中基本语句重复执行的次数是问题规模n的某个函数f(n),算法的时间量度记作:T(n)=O(f(n))
它表示随着n的增大,算法执行的时间的增长率和f(n)的增长率相同,称渐进时间复杂度

基本语句是:

  • 算法中重复执行次数和算法的执行时间成正比的语句
  • 对算法运行时间的贡献最大
  • 执行次数最多

问题规模n表示处理问题量的多少
n越大算法的执行时间越长

+ 排序:n为记录数
  • 矩阵:n为矩阵的阶数
  • 多项式:n为多项式的项数
  • 集合:n为元素个数
  • 树:n为树的结点个数
  • 图:n为图的顶点数或边数

计算算法时间复杂度

定理:忽略所有低次幂项和最高次幂系数,体现出增长率的含义

  1. 找出语句频度最大的那条语句作为基本语句
  2. 计算基本语句的频度得到问题规模n的某个函数f(n)
  3. 取其数量级用符号O表示
x = 0, y = 0;
for (int k = 0; k < n; k++)
    x++;
for (int i = 0; i < n; i++)
    for (int j = 0; j < n; j++)//f(n)=n(n+1)
        y++;

该程序时间复杂度为O(n2)
时间复杂度是由嵌套最深层语句的频度决定的

void exam(float x[][], int m, int n) {
    float sum[];
    for (int i = 0; i < m; i++) {
        sum[i] = 0.0;
        for (int j = 0; j < n; j++)
            sum[i] += x[i][j];//f(n)=m*n
    }
    for (i = 0; i < m; i++)
        cout << i << '=' << sum[i] << endl;
}

该程序时间复杂度为O(m*n)

for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++) {
    c[i][j] = 0;
    for (k = 1; k <= n; k++)
        c[i][j] = c[i][j] + a[i][k] * b[K][j];
}

$$
T(n)=\sum_in\sum_jn\sum_k^n=
\sum_in\sum_jn n=
\sum_inn2=
n^3=
O(n^3)
$$

for (i = 1; i <= n; i++)
	for (j = 1; j <= i; j++)
		for (k = 1; k <= j; k++)
			x = x + 1;

$$
语句频度=\sum_in\sum_ji\sum_k^j1=
\sum_in\sum_jij=
\sum_i^n\frac{i(i+1)}{2}
$$

$$
\sum_i^n\frac{i(i+1)}{2}=
\frac{1}{2}(\sum_ini2+\sum_i^ni)=
\frac{1}{2}(\frac{n(n+1)(2n+1)}{6}+\frac{n(n+1)}{2})=
\frac{n(n+1)(n+2)}{6}
$$

i=1;
while(i<=n)
    i*=2;

若循环执行1次:i=1x2=2
若循环执行2次:i=2x2=22
若循环执行3次:i=22x2=23
若循环执行x次:i=2x

2x<=n 等于 x<=log2n

2f(n)<=n 即 f(n) <=log2n

该程序时间复杂度T(n)=O(log2n)

算法渐进时间复杂度

for (i = 0;i < n;i++)
    if (a[i]===e) return i+1;
return 0;

最好情况:1次
最坏情况:n次
平均时间复杂度:O(n)

  • 最坏时间复杂度:指在最坏情况下,算法的时间复杂度

  • 平均时间复杂度:指在所有可能输入实例在等概率出现的情况下,算法的期望运行时间

  • 最好时间复杂度:指在最好情况下,算法的时间复杂度

  • 一般总是考虑在最坏情况下的时间复杂度,以保证算法的运行时间不会比它更长

  • 对于复杂的算法,可以将它分成几个容易估算的部分,然后利用大O加法法则和乘法法则,计算算法的时间复杂度

    • 加法规则
    • T1(n)+T2(n)=O(f(n))+O(g(n))=O(max( (f(n),g(n)) )
    • 乘法规则
    • T1(n)xT2(n)=O(f(n))xO(g(n))=O( (f(n)xg(n)) )

算法渐进空间复杂度

  • 空间复杂度:算法所需存储空间的度量记作:S(n)=O(f(n))
    其中n为问题的规模或大小
  • 算法要占据的空间
    • 算法本身要占据的空间,输入/输出,指令,常数,变量等
    • 算法要使用的辅助空间

将一维数组a中的n个数逆序存放到原数组中的两个算法:

//algorithm one
for (i = 0; i < n / 2; i++) {
    t = a[i];
    a[i] = a[n - i - 1];
    a[n - i - 1] = t;
}

//algorithm two
for (i = 0; i < n; i++)
     b[i] = a[n - i - 1];
for (i = 0; i < n; i++)
     a[i] = b[i];
}

第一个算法原地工作S(n)=O(1),第二个算法S(n)=O(n)

抽象数据类型=数据的逻辑结构+抽象运算(运算的功能描述)

线性表

  • 线性表是由n(n>=0)个具有相同特性的数据元素(结点)a1,a2,...an组成的有限序列
  • 下标(索引)是元素的序号,表示元素在表中的位置
  • 数据元素的个数为表的长度

同一线性表中的元素必定有相同特性,数据元素间的关系是线性关系

线性表的逻辑特征是

  • 在非空的线性表,有且仅有一个开始结点a1,它没有直接前趋而仅有一个直接后继a2
  • 有且仅有一个终端结点an,它没有直接后继,而仅有一个直接前趋an-1
  • 其余的内部结点ai (2<=i<=n-1) 都有且仅有一个直接前趋ai-1,和一个直接后继ai+1
  • 顺序存储结构存在问题
    1. 存储空间分配不灵活
    2. 运算的空间复杂度高

线性表的顺序表示又称为顺序存储结构或顺序映像
顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻存储单元中的存储结构

若线性表每个元素占用i个存储单元,则第n个数据元素和n+1数据元素存储位置关系为:

LOCAL(an+1)=LOCAL(an)+i

数组静态分配

using SqList = struct {
	ElemType data[MAXSIZE];
    //ElemType是任意的元素类型,伪代码
    int length;
}SqList

数组动态分配

  	//C++动态存储分配
	new typename(初值列表) 
    //功能:申请用于存放typename对象的内存空间,并依初值列表赋以初值结果值
    //成功:typename类型的指针,指向新分配的内存
    //失败:0(NULL)
	int *ptr=new int(10);
delete pointerP
    //功能:释放指针P所指向的内存,P必须是new操作返回的操作值

线性表头文件

#ifndef  _SEQUENCELIST_H
#define  _SEQUENCELIST_H

#include <iostream>
#include <string>

#define MAXSIZE 100

using std::cerr;
using std::endl;
using std::string;

using Book = struct {
    string no[20];  //Book ISBN
    string name[50];//Book name
    float price;  //Book price
};

using SqList = struct {
    Book* elem;//Base address of the storage space
    //基地址指线性表的第1个数据元素的存储位置
    int length;//The current number of Books
};

//Linear table initialization
bool InitList(SqList& L)
{
    L.elem = new Book[MAXSIZE]; //在堆区开辟内存
    if (!L.elem)
    {
        cerr << "error" << endl;
        exit(OVERFLOW);
        //return false;
    }
    L.length = 0;
    return true;
}
//Linear list destruction
void DestroyList(SqList& L)
{
    if (L.elem)
        delete L.elem;
}
//Linear list empty
void CLearList(SqList& L)
{
    L.length = 0;
}
//Linear list length
int GetLength(SqList& L) {
    return (L.length);
}
//Determines whether the linear table is empty
bool IsEmpty(const SqList& L)
{
    return static_cast<bool>(L.length);
    //使用了static_cast<bool>将顺序表的长度(length)转换为布尔值
}
//Linear list value
bool GetELem(const SqList& L, const int i, Book& e)
{
    if (i<1 || i>L.length)
    {
        cerr << "out of range" << endl;
        return false;
    }
    e = L.elem[i - 1];
    return true;
}
//Linear list lookup
int LocateList(const SqList& L, const double & e)
{
    for (int i = 0; i < L.length; ++i)
        if (L.elem[i].price == e)
            return i + 1;
            //查找成功,返回其查找元素的第一个下标值
    return false;//未能找到对应元素,返回0
   
    //另一种方式
    /*while(i<L.length && L.elem[i] !=e)
        ++i;
    if(i<L.length)
        return i+1;
    return false;*/
}
//Linear list insertion
bool InsertList(SqList& L, const Book& e, const int& i)
{
    if (L.length == MAXSIZE)
    {
        cerr << "can not insert!" << endl;
        return false;
    }
    if (i<1 || i>L.length)
        //i在这里是位序,不是数组下标
    {
        cerr << "wrong insert position!" << endl;
        return false;
    }
    if (L.length > 0)
        //将位于插入位置及之后的元素依次向后挪动一位
        for (int j = L.length - 1; j >= i - 1; --j)
            L.elem[j + 1] = L.elem[j];
    
    //插入元素
    L.elem[i-1] = e;
    //线性表长度+1
    ++L.length;
    return true;
}
//Linear list deletion
bool EraseList(SqList& L, const int& i)
{
    //异常判断
    if (i<1 || i>L.length)
    {
        cerr << "wrong erase position!" << endl;
        return false;
    }
    if (L.length == 0)
    {
        cerr << "List has no length" << endl;
        return false;
    }
    //将位于删除位置之后的元素依次向前挪动一位
    for (int j = i; j < L.length; ++j)
        L.elem[j - 1] = L.elem[j];
    //线性表长度-1
    
    --L.length;
    return true;
}
#endif

顺序表优缺点

  • 优点:
    • 存储密度大(结点本身所占存储量/结点结构所占存储量)
    • 可以随机存取表中任一元素
  • 缺点:
    • 在插入、删除某一元素时,需要移动大量元素
    • 浪费存储空间
    • 属于静态存储形式,数据元素的个数不能自由扩充

线性表的链式存储结构

节点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
线性表的链式表示又称为非顺序映像或链式映像

  • 用一组物理位置任意的存储单元来存放线性表的数据元素
  • 这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上
  • 链表中元素的逻辑次序和物理次序不一定相同

各节点由两个域组成

  • 数据域:存储元素数值数据

  • 指针域:存储直接后继节点的存储位置

  • 头指针:指向链表第一个节点的指针

  • 首元节点:链表中存储第一个数据元素a1的节点

  • 头节点:链表的首元节点之前附设的一个节点

无头节点时,头节点为空表示空表

有头节点时,头节点的指针域为空表示空表

链表中设置头节点的好处

  1. 便于首元节点的处理

    首元节点的地址保存在头节点的指针域中,所以在链表的第一个位置上的操作和其它位置一致,无须进行特殊处理

  2. 便于空表和非空表的统一处理

    无论链表是否为空,头指针都是指向头节点的非空指针,因此空表和非空表的处理也就统一了

单链表

结点只有一个指针域的链表,称为单链表或线性链表

判断链表是否为空

bool IsEmpty(const LinkList &L) {
    if(L->next)//非空
        return false;
    else 
        return true;
}

销毁链表

bool DestroyList(LinkList &L) {
    //判断链表是否为空
    if(IsEmpty(L))
    {
        cerr << "empty List!" << endl;
        return false;
    }
    while(L)//链表还未到达尾端
    {
        auto temp = L->next;//将头指针指向下一个节点
        delete L;
        L = temp;
    }
    return true;
}

清空链表

bool ClearList(LinkList &L) {
    LinkList p = L->next;
    while(p){
		auto q = p->next;
        delete p;
        p = q;
    }
    L->next=nullptr;
    return true;
}

求链表长度

size_t GetLength(const LinkList &L) {
    Lnode *p = L->next;//等价于LinkList p
    size_t cnt = 0;
    while (p)
    {
        ++cnt;
        p = p->next;
    }
    return cnt;
}

取第 i 个元素的值

bool GetElem(const LinkList &L, const int &i, const ElemType &e) {
    if(i < 0)
    {
        cerr << "out of range" << endl;
        return false;
    }
    Lnode *p = L->next;
    while(p && j < i) {
        p = p->next;
        ++j;
        if(!p || j > i) {
            cerr << "out of range" << endl;
            return false;
            //如果此时p为空,意味着已经到达链表尾端,停止循环
        }
    }
    e = p->data;
    return true;
}

按值查找链表,根据指定数据获取位置序号

size_t LocateElem(const LinkList &L, const ElemType &e) {
    Lnode *p = L->next;
    size_t cnt = 1;
    while (p)
    {
        if (p->data == e)
            return cnt;
        ++cnt;
        p = p->next;
    }
    cerr << "not found" << endl;
    return false;
}

在链表中插入元素

bool InsertList(LinkList &L, const size_t &i, const ElemType &e) {//在L中第i个元素之前插入数据元素e
    Lnode *p = L;
    size_t j = 0;
    while(p && j < i - 1)
    {
        p = p->next;
        ++j;
    }
    //异常判断
    if(!p || i < 0)
    {
        cerr << "out of range" << endl;
        return false;
    }
    LinkList insert = new Lnode;
    insert->data = e;
    //生成新节点并将值插入
    insert->next = p->next;
    //新节点next指针指向第i-1个节点的下一个节点也就是第i个节点
    p->next = insert;
    //第i-1节点next指针指向新节点
    /*i-1指向insert,insert指向i,insert插入在第i个节点之前*/
    return true;
}

删除链表的节点

bool EraseList(LinkList &L, const int &i) {
    //删除第i个数据元素
    Lnode *p = L;
    int j = 0;
    while (p->next && j < i - 1)
    {
        p = p->next;
        ++j;
    }
    if (!(p->next) || i < 0)
    {
        cerr << "out of range" << endl;
        return false;
    }
    Lnode *q = p->next;
    p->next = p->next->next;
    delete q;
    return true;
}

头插法创建单链表

void CreatListHead(LinkList &L, const size_t n) {
    L = new LNode;
    L->next = nullptr;//先建立带头节点的单链表
    //删除它们链表将不再带头节点
    for (int i = 0; i < n; ++i)
    {
        Lnode *p = new Lnode;
        cin >> p->data;
        p->next = L->next;//插入到表头
        L->next = p;//L指向
    }
}

尾插法创建单链表

void CreatListTail(LinkList &L, const size_t n) {
    //n代表节点个数
    L = new Lnode;
    L->next = nullptr;
    Lnode *r = L;//尾指针r指向头节点
    for (int i = 0; i < n; ++i)
    {
        Lnode *p = new Lnode;
        cin >> p->data;
        p->next = r->next;
        r->next = p;//插入到表尾
        r = r->next;//r指向新尾节点
    }
}
#include<iostream>
using namespace std;
#define Status int
#define ElemType int

typedef struct Lnode
{
	ElemType data;//数据域
	struct Lnode* next;//指针域
}Lnode, * LinkList;

//**************************基本操作函数***************************//
//初始化函数
bool InitList(LinkList& L)
{
	L = new Lnode;//生成头结点 这样删除等操作就不必分第一个结点和其他了
	L->next = nullptr;  //空表 也就是说头结点的指针指向为空
	return true;
}
//获取单链表长度 头结点无数据,不算长度
int ListLength(LinkList L)
{
	LinkList p = L; int sum = 0;
	while (p)
	{
		sum++;
		p = p->next;
	}
	return sum - 1;//去除头结点
}
//插入函数--后插法 插入到第i(1<=i<=length+1)个位置 即i-1之后 不必区分i的位置
bool ListInsert(LinkList& L, int i, ElemType e)
{
	Lnode* s; LinkList p = L; int j = 0;
	while (p && (j < i - 1))//j指到i-1位置或者p已经到最后时跳出
	{
		p = p->next;
		++j;
	}
	if (!p || j > i - 1)//i<1或者i>ListLength(L)+1时,插入位置无效 不调用ListLength,提高效率
	{
		cout<<"插入位置无效!"<<endl;
		return false;
	}
	s = new Lnode;
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}
//删除函数 删除位置i的结点 即删除i-1之后的结点
bool ListDelete(LinkList& L, int i)
{
	Lnode* s; LinkList p = L; int j = 0;
	LinkList q;
	while (p && (j < i - 1))//j指到i-1位置
	{
		p = p->next;
		++j;
	}
	if (p == nullptr || !(p->next) || j > i - 1)//i<1或者i>ListLength(L)时,删除位置无效
	{
		cout << "删除位置无效!" << endl;

		return false;
	}
	q = p->next;
	p->next = q->next;
    
    delete q;
	return true;
}
//查找函数 按值查找 查找第一个等于e的结点 成功返回该结点指针,否则返回NULL
Lnode* LocateElem(LinkList L, ElemType e)
{
	Lnode* p = L;
	while (p && (p->data != e))
	{
		p = p->next;
	}
	return p;
}
//**************************功能实现函数**************************//
//遍历输出函数
void PrintList(LinkList L)
{
	LinkList p = L->next;//跳过头结点
	if (ListLength(L))
	{
		cout << "当前单链表所有元素:" << endl;

		while (p)
		{
			cout << p->data;
			p = p->next;
		}
		printf("\n");
	}
	else
	{
		printf("当前单链表已空!\n");
	}
}
//插入功能函数 调用ListInsert后插
void Insert(LinkList& L)
{
	int place; ElemType e; bool flag;
	printf("请输入要插入的位置(从1开始)及元素:\n");
	cin >> place >> e;
	flag = ListInsert(L, place, e);
	if (flag)
	{
		cout << "插入成功!" << endl;
		PrintList(L);
	}
}
//删除功能函数 调用ListDelete删除
void Delete(LinkList L)
{
	int place; bool flag;
	printf("请输入要删除的位置(从1开始):\n");
	cin >> place;
	flag = ListDelete(L, place);
	if (flag)
	{
		printf("删除成功!!!\n");
		PrintList(L);
	}
}
//查找功能函数 调用LocateElem查找
void Search(LinkList L)
{
	ElemType e; Lnode* q;
	printf("请输入要查找的值:\n");
	cin >> e;
	q = LocateElem(L, e);
	if (q)
	{
		printf("找到该元素!\n");
	}
	else
		printf("未找到该元素!\n");
}
//菜单
void menu()
{
	printf("********1.后插    2.删除*********\n");
	printf("********3.查找    4.输出*********\n");
	printf("********5.退出          *********\n");
}
//主函数
int main()
{
	LinkList L; int choice;
	InitList(L);
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");
		cin >> choice;
		if (choice == 5) break;
		switch (choice)
		{
		case 1:Insert(L); break;
		case 2:Delete(L); break;
		case 3:Search(L); break;
		case 4:PrintList(L); break;
		default:printf("输入错误!!!\n");
		}
	}
	return 0;
}

双向链表

结点有两个指针域的链表,称为双向链表

在单链表的基础上,对于每一个结点设计一个前驱结点,前驱结点与前一个结点相互连接,构成一个链表

#include <iostream>
using namespace std;
template<class T> 
class doubleLinkedList;
//声明一下双向链表,以免定义友元时报错
template <class T>
class doubleLinkedListNode
{
	private:
    	//双向结点前驱指针指向该结点的前驱结点
		doubleLinkedListNode<T> *prior;
    	//储存结点数据
		T data;
    	//双向结点的后驱指针指向该结点的后继结点
		doubleLinkedListNode<T> *next;
		//将双向链表类定义为结点的友元类
		friend  class doubleLinkedList<T>;
	public:
		//结点的无参构造函数,将结点指针域初始化为NULL
		doubleLinkedListNode()
		{
			prior = NULL;
			next = NULL;
		}
		//结点的有参构造函数,初始化指针域和数据域
		doubleLinkedListNode(T _data,doubleLinkedListNode<T> *_prior = NULL,doubleLinkedListNode<T> *_next = NULL)
		{
			prior = _prior;//初始化前驱指针
			data = _data;//初始化数据域
			next = _next;//初始化后继指针
		}
		~doubleLinkedListNode()
		{
			prior = NULL;
			next = NULL;
		}
};
template <class T>
class doubleLinkedList
{
	private:
		doubleLinkedListNode<T> *head;//双向链表的头指针
		int length;//双向链表的长度
	public:
		//双向链表的构造函数,链表产生新头结点
		doubleLinkedList()
		{
			head = new doubleLinkedListNode<T>();//链表产生新头结点
			length = 0;
		}
		//双向链表的构造函数,链表产生新头结点
		doubleLinkedList(doubleLinkedListNode<T> *note)
		{
			head = note;
			length = 0;
		}
		//双向链表的析构函数,链表删除头节点
		~doubleLinkedList()
		{
			delete head;
		}
		//双链表插入结点----头插法
		bool insertNodeByhead(T item);
		//双向链表插入结点----尾插法
		bool insertNodeBytail(T item);
		//在第i个结点插入T类型item
		bool insertNode(T item,int n);
		//寻找给定数值的结点
		bool findData(T item);
		//删除第i个结点的数据
		bool deleteData(int n);
		//获取双向链表长度
		int getLength();
		//修改指定位置元素,被修改元素以引用返回
		bool changeListElements(int n,T item,T &x);
		//打印双向链表
		void printLinkedlist();
};
template<class T>
int doubleLinkedList<T>::getLength()
{
	doubleLinkedListNode<T> *pMove = head->next;  //设置游标指针
	int length=0;
	//遍历链表,计算结点数
	while(pMove!=NULL)
	{
		pMove = pMove->next;  //游标指针后移
		length++;       //计算length
	}
	return length;
}
template<class T>
bool doubleLinkedList<T>::insertNode(T item,int n)
{
	if(n<1){
		cout<<"输入的非有效位置!"<<endl;
		return false;
	}
	doubleLinkedListNode<T>* pMove = head;//创建一个新的指针,设置为游标指针
	//首先找到插入位置
	for(int i=1;i<n;i++)
	{
		pMove = pMove->next;
		if(pMove == NULL&& i<=n)
		{
			cout<<"插入位置无效!"<<endl;
			return false;
		}
	}
	//创建一个新的结点
	doubleLinkedListNode<T>* newNode = new doubleLinkedListNode<T>(item);
	if (newNode == NULL){
		cout << "内存分配失败,新结点无法创建" << endl;
		return false;
	}
	//插入新的结点
	newNode->next = pMove->next;   
	if (pMove->next != NULL)
	{
		pMove->next->prior = newNode;
	}
	newNode->prior = pMove;
	pMove->next = newNode;
	return true;
}
template<class T>
bool doubleLinkedList<T>::insertNodeByhead(T item)
{
	//创建一个新的结点
	doubleLinkedListNode<T>* newNode = new doubleLinkedListNode<T>(item);
	if (newNode == NULL){
		cout << "内存分配失败,新结点无法创建" << endl;
		return false;
	}
	//分两种情况,head的next是否为NULL,然后处理四个指针
	if(head->next == NULL)
	{
		head->next = newNode;
		newNode->prior = head;
		return true;
	}
	else{
		newNode->next = head->next;
		head->next->prior = newNode;
		newNode->prior = head;
		head->next = newNode;
		return true;
	}
}
template<class T>
bool doubleLinkedList<T>::insertNodeBytail(T item)
{
	//创建一个新的结点
	doubleLinkedListNode<T>* newNode = new doubleLinkedListNode<T>(item);
	if (newNode == NULL){
		cout << "内存分配失败,新结点无法创建" << endl;
		return false;
	}
	//首先找到最后一个结点
	doubleLinkedListNode<T>* lastNode = head;
	while(lastNode->next != NULL)
	{
		lastNode = lastNode->next;//没找到就一直循环
	}
	//找到调整指针
	lastNode->next = newNode;
	newNode->prior = lastNode;
	return true;
}
template<class T>
void doubleLinkedList<T>::printLinkedlist()
{
	//从第二个结点开始打印,表头不含数据
	doubleLinkedListNode<T>* pMove = head->next;
	while(pMove)
	{
		cout<<pMove->data<<" ";
		pMove = pMove->next;//移动指针
	}
	cout<<endl;
}
template<class T>
bool doubleLinkedList<T>::findData(T item)
{
	doubleLinkedListNode<T> *pMove = head->next;  //设置游标指针
	if(pMove == NULL)//链表为空
	{
		return false;
	}
	while(pMove)//遍历链表
	{
		if(pMove->data == item){
			return true;
		}
		pMove = pMove->next;
	}
	return false;
}
template<class T>
bool doubleLinkedList<T>::deleteData(int n)
{
	if (n<1||n>getLength())
	{
		cout << "输入非有效位置" << endl;
		return false;
	}
	doubleLinkedListNode<T> * pMove = head;//设置游标指针
	doubleLinkedListNode<T> * pDelete;             
	//查找删除结点的位置
	for (int i = 1; i <= n; i++)
	{
		pMove = pMove->next;                                         //游标指针后移
	}
	//删除结点
	pDelete = pMove;      
	pMove->prior->next = pDelete->next;
	pMove->next->prior = pDelete->prior;
	delete pDelete;//释放空间
	return true;
}
template<class T>
bool doubleLinkedList<T>::changeListElements(int n,T item,T &x)
{
	if (n<1||n>getLength())
	{
		cout << "输入非有效位置" << endl;
		return false;
	}
	doubleLinkedListNode<T> *pMove = head->next;  //设置游标指针
	for(int i=1;i<n;i++)//找到指定位置1
	{
		pMove = pMove->next;
	}
	x = pMove->data;
	pMove->data = item;
	return true;
}

#include "DoubleLinkedList.h"
void Menu()
{
	cout<<"+===========================================+"<<endl;
	cout<<"|    1.测试头部插入元素      2.测试尾部插入元素  |"<<endl;
	cout<<"+===========================================+"<<endl;
	cout<<"|    3.指定位置插入元素      4.测试双向链表长度  |"<<endl;
	cout<<"+===========================================+"<<endl;
	cout<<"|    5.指定位置修改元素      6.指定位置删除元素  |"<<endl;
	cout<<"+===========================================+"<<endl;
	cout<<"|    7.查找元素是否存在      8.打印双向链表     |"<<endl;
	cout<<"+===========================================+"<<endl;
	cout<<"|    0.退出                                 |"<<endl;
	cout<<"+===========================================+"<<endl;
}
int main() 
{
	doubleLinkedListNode<int> node;
	doubleLinkedList<int> list;
	for(int i=1;i<10;i++)
	{
		list.insertNodeBytail(i);
	}
	Menu();
	int choose=1;
	int tmp,n;
	while(choose){
		cout<<"请输入你的选择:"<<endl;
		cin>>choose;
	switch (choose) {
		case 1:
			{
				cout<<"请输入你要插入的元素:";
				cin>>tmp;
				list.insertNodeByhead(tmp);
			}
			break;
		case 2:
			{
				cout<<"请输入你要插入的元素:";
				cin>>tmp;
				list.insertNodeBytail(tmp);
			}
			break;
		case 3:
			{
				cout<<"请输入你要插入的位置:"<<endl;
				cin>>n;
				cout<<"请输入你要插入的元素:";
				cin>>tmp;
				list.insertNode(tmp,n);
			}
			break;
		case 4:
			{
				cout<<"双向链表的长度为:"<<list.getLength()<<endl;
			}
			break;
		case 5:
			{
				int x;
				cout<<"请输入你要修改的位置:"<<endl;
				cin>>n;
				cout<<"请输入你要修改的元素:";
				cin>>tmp;
				list.changeListElements(n,tmp, x);
				cout<<"你将"<<n<<"号位置的元素"<<x<<"修改成了元素"<<tmp<<endl;
			}
			break;
		case 6:
			{
				cout<<"请输入你要删除的位置:"<<endl;
				cin>>n;
				list.deleteData(n);
			}
			break;
		case 7:
			{
				cout<<"请输入你要查找的元素:"<<endl;
				cin>>tmp;
				if(list.findData(tmp)==true)
				{
					cout<<"该元素存在!"<<endl;
				}
				else{
					cout<<"该元素不存在!"<<endl;
				}
			}
			break;
		case 8:
			{
				list.printLinkedlist();
			}
			break;
		case 0:
			return 0;
			break;
		default:
			break;
		cout << endl;
		Menu();
	}
	cout<<endl;
	Menu();
	}

	return 0;
}

循环链表

首尾相接的链表称为循环链表

从任一节点出发均可找到其他节点

循环链表中没有NULL指针,非循环链表是判断p或p->next是否为空,循环链表是判断p或p->next是否等于头指针

#pragma once

#include <iostream>

template<typename T>
struct CircLinkNode {
	T data;
	CircLinkNode<T>* link;
	CircLinkNode(CircLinkNode<T>* next = nullptr) :data(0), link(next) {}
	CircLinkNode(T d, CircLinkNode<T>* next = nullptr) :data(d), link(next) {}
};

template<typename T>
class CircList
{
public:
	CircList();												//构造函数
	CircList(const T& x);									//构造函数
	CircList(CircList<T>& L);								//拷贝构造
	~CircList();											//析构函数
	void makeEmpty();										//将链表置空
	int Length()const;										//计算循环链表长度
	bool IsEmpty() { return first->link == first ? true : false; }
															//判表空否
	CircLinkNode<T>* getHead()const;						//返回附加头结点地址
	void setHead(CircLinkNode<T>* p);						//设置附加头结点地址
	CircLinkNode<T>* Search(T x);							//搜索含数据x的元素
	CircLinkNode<T>* Locate(int i)const;					//搜索第i个元素的地址
	bool getData(int i, T& x)const;							//取出第i个元素的地址
	void setData(int i, T& x);								//用x修改第i个元素的值
	bool Insert(int i, T& x);								//在第i个元素后插入x
	bool Remove(int i, T& x);								//删除第i个元素,x返回该元素的值
	void output();											//输出
	CircList<T>& operator=(CircList<T>& L);					//重载运算符=
	void inputFront(T endTag);								//前插法建立单链表
	void inputRear(T endTag);								//后插法建立单链表
private:
	CircLinkNode<T>* first, * last;						//头指针,尾指针
};

template<typename T>
inline CircList<T>::CircList()
{
	first = new CircLinkNode<T>;
	first->link = first;
	last = first;
}

template<typename T>
inline CircList<T>::CircList(const T& x)
{
	first = new CircLinkNode<T>(x);
	first->link = first;
	last = first;
}

template<typename T>
inline CircList<T>::CircList(CircList<T>& L)
{
	//拷贝构造函数
	T value;
	CircLinkNode<T>* srcptr = L.getHead();					//被赋值表的附加头结点地址
	CircLinkNode<T>* destptr = first = new CircLinkNode<T>;
	while (srcptr->link != L.first)							//逐个结点复制
	{
		value = srcptr->link->data;
		destptr->link = new CircLinkNode<T>(value);
		destptr = destptr->link;
		srcptr = srcptr->link;
	}
	last = srcptr;
	destptr->link = first;	
}

template<typename T>
inline CircList<T>::~CircList()
{
	makeEmpty();
	if (first != nullptr) delete first;
}

template<typename T>
inline void CircList<T>::makeEmpty()
{
	//将链表置位空表
	CircLinkNode<T>* q;
	while (first->link != first)
	{
		q = first->link;
		first->link = q->link;
		delete q;
	}
	last = first;
}

template<typename T>
inline int CircList<T>::Length() const
{
	//计算带附加头结点的单链表的长度
	CircLinkNode<T>* p = first->link;
	int count = 0;
	while (p != first)										//循链扫描,寻找链尾
	{
		p = p->link;
		count++;
	}
	return count;
}

template<typename T>
inline CircLinkNode<T>* CircList<T>::getHead() const
{
	return first;
}

template<typename T>
inline void CircList<T>::setHead(CircLinkNode<T>* p)
{
	CircLinkNode<T>* current = first;
	while (current->link != first)
	{
		if (current->link == p) break;						//循链找p指针
		else current = current->link;
	}
	first = current->link;
	last = current;
}

template<typename T>
inline CircLinkNode<T>* CircList<T>::Search(T x)
{
	//在表中搜索含数据x的节点,搜索成功时函数返回该节点地址;否则返回first值。
	CircLinkNode<T>* current = first->link;
	while (current != first)
	{
		if (current->data == x) break;						//循链找含x结点
		else current = current->link;
	}
	return current;
}

template<typename T>
inline CircLinkNode<T>* CircList<T>::Locate(int i) const
{
	//定位函数。返回表中第i个元素的地址。若i<0或i超过表中结点个数,则返回first。
	if (i < 0) return first;								//i不合理
	CircLinkNode<T>* current = first->link;
	int k = 1;
	while (current != first && k < i)						//循链找第i个结点
	{
		current = current->link;
		k++;
	}
	return current;											//返回第i个结点地址,若返回first,表示i值太大
}

template<typename T>
inline bool CircList<T>::getData(int i, T& x) const
{
	//取出链表中第i个元素的值。
	if (i <= 0) return false;								//i值太小
	CircLinkNode<T>* current = Locate(i);
	if (current == first) return false;						//i值太大
	else
	{
		x = current->data;
		return true;
	}
}

template<typename T>
inline void CircList<T>::setData(int i, T& x)
{
	//给链表中的第i个元素赋值x。
	if (i <= 0) return;										//i值太小
	CircLinkNode<T>* current = Locate(i);
	if (current == first) return;							//i值太大
	else current->data = x;
}

template<typename T>
inline bool CircList<T>::Insert(int i, T& x)
{
	//将新元素x插入在链表中第i个结点之后。
	CircLinkNode<T>* current = Locate(i);
	if (current == first) return false;						//插入不成功
	CircLinkNode<T>* newNode = new CircLinkNode<T>(x);
	if (newNode == nullptr)
	{
		std::cerr << "存储分配错误!" << std::endl;
		exit(1);
	}
	newNode->link = current->link;
	current->link = newNode;
	if (newNode->link == first) last = newNode;
	return true;											//插入成功
}

template<typename T>
inline bool CircList<T>::Remove(int i, T& x)
{
	//将链表中的第i个元素删去,通过引用型参数x返回该元素的值。
	CircLinkNode<T>* current = Locate(i - 1);
	if (current == first || current->link == first)	return false;
	//删除不成功
	CircLinkNode<T>* del = current->link;					//重新拉链,将被删结点从链中摘下
	current->link = del->link;
	x = del->data;
	delete del;												//取出被删结点中的数据值
	if (current->link == first) last = current;
	return true;
}

template<typename T>
inline void CircList<T>::output()
{
	//循环链表的输出函数:将循环链表中所有数据按逻辑顺序输出到屏幕上。
	std::cout << "链表中的元素为: ";
	CircLinkNode<T>* current = first->link;
	while (current != first)
	{
		std::cout << current->data << " ";
		current = current->link;
	}
	std::cout << std::endl;
}

template<typename T>
inline CircList<T>& CircList<T>::operator=(CircList<T>& L)
{
	//重载函数:赋值操作,形式如 A = B,其中 A 是调用此操作的List对象,B 是参数表中的
	//引用型参数L结合的实参。
	makeEmpty();
	T value;
	CircLinkNode<T>* srcptr = L.getHead();					//被复制表的附加头结点地址
	CircLinkNode<T>* destptr = first = new CircLinkNode<T>;
	while (srcptr->link != L.first)							//逐个结点复制
	{
		value = srcptr->link->data;
		destptr->link = new CircLinkNode<T>(value);
		destptr = destptr->link;
		srcptr = srcptr->link;
	}
	destptr->link = first;
	last = destptr;
	return *this;											//返回操作对象地址
}

template<typename T>
inline void CircList<T>::inputFront(T endTag)
{
	//endTag是约定的输入序列结束的标志。如果输入序列是正整数,endTag可以是0或负数;
	//如果输入序列是字符,endTag可以是字符集中不会出现的字符,如"\0"。
	CircLinkNode<T>* newNode;
	T val;
	makeEmpty();
	std::cin >> val;
	while (val != endTag)
	{
		newNode = new CircLinkNode<T>(val);					//创建新结点
		if (newNode == nullptr)
		{
			std::cerr << "存储分配错误!" << std::endl;
			exit(1);
		}
		newNode->link = first->link;
		first->link = newNode;								//插入到表最前端
		if (last == first)
		{
			last = newNode;
		}
		std::cin >> val;
	}
}

template<typename T>
inline void CircList<T>::inputRear(T endTag)
{
	//endTag是约定的输入序列结束的标志
	CircLinkNode<T>* newNode;
	T val;
	makeEmpty();
	std::cin >> val;	
	while (val != endTag)									//last指向表尾
	{
		newNode = new CircLinkNode<T>(val);
		if (newNode == nullptr)
		{
			std::cerr << "存储分配错误!" << std::endl;
			exit(1);
		}		
		last->link = newNode;
		last = newNode;
		std::cin >> val;									//插入到表末端
	}
	last->link = first;
}

#include "CircList.h"

int main()
{
	CircList<int> L1;
	CircList<int> L2;
	int val = 0;
	bool flag = false;

	std::cout << "Q:测试IsEmpty()  链表L1是否为空" << std::endl;
	flag = L1.IsEmpty();
	if (flag) std::cout << "链表L1为空" << std::endl;
	else std::cout << "链表L1不为空" << std::endl;

	std::cout << "Q:测试inputFront()  前插法建立单链表L1" << std::endl;
	L1.inputFront(-1);
	L1.output();

	std::cout << "Q:测试IsEmpty()  链表L1是否为空" << std::endl;
	flag = L1.IsEmpty();
	if (flag) std::cout << "链表L1为空" << std::endl;
	else std::cout << "链表不为空" << std::endl;

	std::cout << "Q:测试Length()  链表L1的长度为:" << std::endl;
	std::cout << L1.Length() << std::endl;

	std::cout << "Q:测试getData()  链表L1取第3个元素的值:" << std::endl;
	L1.getData(3, val);
	std::cout << val << std::endl;

	std::cout << "Q:测试setData()  链表L1修改第6个元素的值为99" << std::endl;
	val = 99;
	L1.setData(6, val);
	L1.output();

	std::cout << "Q:测试Insert()  链表L1在第8个元素后插入77" << std::endl;
	val = 77;
	L1.Insert(8, val);
	L1.output();

	std::cout << "Q:测试Remove()  链表L1删除第3个元素" << std::endl;
	L1.Remove(3, val);
	std::cout << "删除的元素为:" << val << std::endl;
	L1.output();

	std::cout << "Q:测试setHead()  链表L1设置第4个元素为头结点" << std::endl;
	CircLinkNode<int>* pnode = L1.Locate(4);
	L1.setHead(pnode);
	L1.output();

	std::cout << "Q:测试inputRear()  后插法建立单链表L2" << std::endl;
	L2.inputRear(-1);
	L2.output();

	std::cout << "Q:测试operator=()  赋值单链表L2" << std::endl;
	L2 = L1;
	L2.output();

	return 0;
}

双向循环链表代码实现

#include <iostream>
using namespace std;
template<class T> 
class doubleCircularLinkedList;
//声明一下双向循环链表,以免定义友元时报错
template <class T>
class doubleCircularLinkedListNode
{
	private:
    	//双向结点前驱指针指向该结点的前驱结点
		doubleCircularLinkedListNode<T> *prior;
    	//储存结点数据
		T data;
    	//双向结点的后驱指针指向该结点的后继结点
		doubleCircularLinkedListNode<T> *next;
		//将双向循环链表类定义为结点的友元类
		friend  class doubleCircularLinkedList<T>;
	public:
		//结点的无参构造函数,将结点指针域初始化为NULL
		doubleCircularLinkedListNode()
		{
			prior = NULL;
			next = NULL;
		}
		//结点的有参构造函数,初始化指针域和数据域
		doubleCircularLinkedListNode(T _data,
         doubleCircularLinkedListNode<T> *_prior= NULL,
           doubleCircularLinkedListNode<T> *_next = NULL)
		{
			prior = _prior;//初始化前驱指针
			data = _data;//初始化数据域
			next = _next;//初始化后继指针
		}
		~doubleCircularLinkedListNode()
		{
			prior = NULL;
			next = NULL;
		}
};
template <class T>
class doubleCircularLinkedList
{
	private:
		doubleCircularLinkedListNode<T> *headNode;//双向循环链表的头指针
		int length;//双向循环链表的长度
	public:
		//双向循环链表的构造函数,链表产生新头结点
		doubleCircularLinkedList()
		{
			headNode = new doubleCircularLinkedListNode<T>();//链表产生头结点,带头结点的双向循环链表的实现
			//满足双向循环链表的头结点初始化,头结点的首指针和尾指针都指向自身
			headNode->prior = headNode;//头节点的首指针指向其本身
			headNode->next = headNode;//头节点的尾指针指向其本身
			length =0;//长度初始化为0
		}
		//双向循环链表的析构函数,链表删除头节点
		~doubleCircularLinkedList()
		{
			delete headNode;
		}
		//双向循环链表插入结点----头插法
		bool insertNodeByhead(T item);
		//双向循环链表插入结点----尾插法
		bool insertNodeBytail(T item);
		//在第i个结点插入T类型item
		bool insertNode(T item,int n);
		//寻找给定数值的结点
		bool findData(T item);
		//删除第i个结点的数据
		bool deleteData(int n);
		//获取双向循环链表长度
		int getLength();
		//修改指定位置元素,被修改元素以引用返回
		bool changeListElements(int n,T item,T &x);
		//打印双向循环链表
		void printLinkedlist();
};
template<class T>
int doubleCircularLinkedList<T>::getLength()
{
	doubleCircularLinkedListNode<T> *pMove = headNode->next;  //设置游标指针
	int length=0;
	//遍历链表,计算结点数
	while(pMove != headNode)
	{
		pMove = pMove->next;  //游标指针后移
		length++;       //计算length
	}
	return length;
}
template<class T>
bool doubleCircularLinkedList<T>::insertNode(T item,int n)
{
	if(n<1){
		cout<<"输入的非有效位置!"<<endl;
		return false;
	}
	doubleCircularLinkedListNode<T>* pMove = headNode;//创建一个新的指针,设置为游标指针
	//首先找到插入位置
	for(int i=1;i<n;i++)
	{
		pMove = pMove->next;
		if(pMove == NULL && i<=n)
		{
			cout<<"插入位置无效!"<<endl;
			return false;
		}
	}
	//创建一个新的结点
	doubleCircularLinkedListNode<T>* newNode = new doubleCircularLinkedListNode<T>(item);
	if (newNode == NULL){
		cout << "内存分配失败,新结点无法创建" << endl;
		return false;
	}
	//插入新的结点
	newNode->next = pMove->next;   
	if (pMove->next != headNode)
	{
		pMove->next->prior = newNode;
	}
	newNode->prior = pMove;
	pMove->next = newNode;
	return true;
}
template<class T>
bool doubleCircularLinkedList<T>::insertNodeByhead(T item)
{
	//创建一个新的结点
	doubleCircularLinkedListNode<T>* newNode = new doubleCircularLinkedListNode<T>(item);
	if (newNode == NULL){
		cout << "内存分配失败,新结点无法创建" << endl;
		return false;
	}
	else{
		newNode->prior = headNode;
		newNode->next = headNode->next;
		headNode->next->prior=newNode;
		headNode->next = newNode;
		return true;
	}
}
template<class T>
bool doubleCircularLinkedList<T>::insertNodeBytail(T item)
{
	//创建一个新的结点
	doubleCircularLinkedListNode<T>* newNode = new doubleCircularLinkedListNode<T>(item);
	if (newNode == NULL){
		cout << "内存分配失败,新结点无法创建" << endl;
		return false;
	}
	//首先找到最后一个结点
	doubleCircularLinkedListNode<T>* lastNode = headNode;
	while(lastNode->next != headNode)
	{
		lastNode = lastNode->next;//没找到就一直循环
	}
	//找到之后调整四个指针
	headNode->prior = newNode;
	newNode->next = headNode;
	lastNode->next = newNode;
	newNode->prior = lastNode;
	return true;
}
template<class T>
void doubleCircularLinkedList<T>::printLinkedlist()
{
	//从第二个结点开始打印,表头不含数据
	doubleCircularLinkedListNode<T>* pMove = headNode->next;
	while(pMove !=headNode)//如果pMove->next != headNode这样写,最后一个结点是不会打印的
	{
		cout<<pMove->data<<" ";
		pMove = pMove->next;//移动指针
	}
	cout<<endl;
}
template<class T>
bool doubleCircularLinkedList<T>::findData(T item)
{
	doubleCircularLinkedListNode<T> *pMove = headNode->next; //设置游标指针
	doubleCircularLinkedListNode<T> *pMoveprior = headNode;//指定结点前一个结点的指针
	//找到指定位置
	while(pMove->data != item)
	{
		pMoveprior = pMove;
		pMove = pMoveprior->next;
		//如果没有找到特殊处理
		if(pMove == headNode)
		{
			return false;
		}
	}
	return true;
}
template<class T>
bool doubleCircularLinkedList<T>::deleteData(int n)
{
	if (n<1||n>getLength())
	{
		cout << "输入非有效位置" << endl;
		return false;
	}
	doubleCircularLinkedListNode<T> * pMove = headNode;//设置游标指针
	doubleCircularLinkedListNode<T> * pDelete;             
	//查找删除结点的位置
	for (int i = 1; i <= n; i++)
	{
		pMove = pMove->next;                                         //游标指针后移
	}
	//删除结点
	pDelete = pMove;      
	pMove->prior->next = pDelete->next;
	pMove->next->prior = pDelete->prior;
	delete pDelete;//释放空间
	return true;
}
template<class T>
bool doubleCircularLinkedList<T>::changeListElements(int n,T item,T &x)
{
	if (n<1||n>getLength())
	{
		cout << "输入非有效位置" << endl;
		return false;
	}
	doubleCircularLinkedListNode<T> *pMove = headNode->next;  //设置游标指针
	for(int i=1;i<n;i++)//找到指定位置1
	{
		pMove = pMove->next;
	}
	x = pMove->data;
	pMove->data = item;
	return true;
}

#include "doubleCircularLinkedList.h"
void Menu()
{
	cout<<"+===========================================+"<<endl;
	cout<<"|    1.测试头部插入元素      2.测试尾部插入元素  |"<<endl;
	cout<<"+===========================================+"<<endl;
	cout<<"|    3.指定位置插入元素      4.测试双向链表长度  |"<<endl;
	cout<<"+===========================================+"<<endl;
	cout<<"|    5.指定位置修改元素      6.指定位置删除元素  |"<<endl;
	cout<<"+===========================================+"<<endl;
	cout<<"|    7.查找元素是否存在      8.打印双向链表     |"<<endl;
	cout<<"+===========================================+"<<endl;
	cout<<"|    0.退出                                 |"<<endl;
	cout<<"+===========================================+"<<endl;
}
int main() 
{
	doubleCircularLinkedListNode<int> node;
	doubleCircularLinkedList<int> list;
	for(int i=1;i<10;i++)
	{
		list.insertNodeBytail(i);
	}
	Menu();
	int choose=1;
	int tmp,n;
	while(choose){
		cout<<"请输入你的选择:"<<endl;
		cin>>choose;
	switch (choose) {
		case 1:
			{
				cout<<"请输入你要插入的元素:";
				cin>>tmp;
				list.insertNodeByhead(tmp);
			}
			break;
		case 2:
			{
				cout<<"请输入你要插入的元素:";
				cin>>tmp;
				list.insertNodeBytail(tmp);
			}
			break;
		case 3:
			{
				cout<<"请输入你要插入的位置:"<<endl;
				cin>>n;
				cout<<"请输入你要插入的元素:";
				cin>>tmp;
				list.insertNode(tmp,n);
			}
			break;
		case 4:
			{
				cout<<"双向链表的长度为:"<<list.getLength()<<endl;
			}
			break;
		case 5:
			{
				int x;
				cout<<"请输入你要修改的位置:"<<endl;
				cin>>n;
				cout<<"请输入你要修改的元素:";
				cin>>tmp;
				list.changeListElements(n,tmp, x);
				cout<<"你将"<<n<<"号位置的元素"<<x<<"修改成了元素"<<tmp<<endl;
			}
			break;
		case 6:
			{
				cout<<"请输入你要删除的位置:"<<endl;
				cin>>n;
				list.deleteData(n);
			}
			break;
		case 7:
			{
				cout<<"请输入你要查找的元素:"<<endl;
				cin>>tmp;
				if(list.findData(tmp)==true)
				{
					cout<<"该元素存在!"<<endl;
				}
				else{
					cout<<"该元素不存在!"<<endl;
				}
			}
			break;
		case 8:
			{
				list.printLinkedlist();
			}
			break;
		case 0:
			return 0;
			break;
		default:
			break;
		cout << endl;
		Menu();
	}
	cout<<endl;
	Menu();
	}

	return 0;
}

栈和队列

  • Stack是后进先出Last In First Out

    仅在表尾进行插入和删除操作的顺序表

    压栈push,出栈pop

  • Queue是先进先出First In First Out

    一端插入(表尾),一端删除(表头)

排序

排序方法的分类

  • 按存储介质可分为:

    • 内部排序:数据量不大、数据在内存,无需内外存交换数据

    • 外部排序:数据量较大、数据在外存(文件排序)
      外部排序时,要将数据分批调入内存来排序,中间结果还要及时放入外存,显然外部排序要复杂得多

  • 按比较器个数可分为:

    • 串行排序:单处理机(同一时刻比较一对元素)
    • 并行排序:多处理机(同一时刻比较多对元素)
  • 按主要操作可分为

    • 比较排序:用比较的方法
      插入排序、交换排序、选择排序、归并排序
    • 基数排序:不比较元素的大小,仅仅根据元素本身的取值确定其有序位置
  • 按辅助空间可分为:

    • 原地排序:辅助空间用量为O(1)的排序方法
      所占的辅助存储空间与参加排序的数据量大小无关

    • 非原地排序
      辅助空间用量超过O(1)的排序方法

  • 按稳定性可分为:

    • 稳定排序:能够使任何数值相等的元素,排序以后相对次序不变

    • 非稳定性排序:不是稳定排序的方法

    • 排序的稳定性只对结构类型数据排序有意义

      ​ 例如按照总分从高到低排序

    • 排序方法是否稳定不是衡量一个排序算法是否优劣的原因

  • 按自然性可分为:

  • 自然排序:输入数据越有序,排序的速度越快的排序方法

  • 非自然排序:不是自然排序的方法

插入排序

#include <iostream>
#include <vector>
using namespace std;

using std::cout;
using std::cin;
using std::endl;
using std::vector;

void InsertSort(vector<int>& nums, int c);
void InsertSort(vector<int>& v);

void print(vector<int>& a, int i);
void insertionSort(vector<int>& a, int n);

void BinaryLnsertionSort(vector<int>& a);

//直接插入排序算法1
int main(void) {
	vector<int> sort = { 2,4,6,8,10,12 };
	InsertSort(sort, 4);
}
void InsertSort(vector<int>& sort, int c) {
	const int n = sort.size();
	sort.resize(n + 1);
	print(sort, -1);
	for (int i = 0; i <= n; ++i) {
		if (c > sort.at(n))
			sort.at(n) = c;
		if (c >= sort.at(i) && c <= sort.at(i + 1)) {
			for (int j = n; j > i + 1; --j) {
				sort.at(j) = sort.at(j-1);
				print(sort, i);
			}
			sort.at(i+1) = c;
			print(sort, i);
			break;
		}
	}
}

//直接插入排序算法2
int main() {
    vector<int> nums = { 8,9,1,4,2,3,6,7,5 };
    cin >> nums.back();
    InsertSort(nums);
};
void InsertSort(vector<int>& v) {
    const int n = v.size();
    for (int i = 1; i < n; ++i) {
//        int key = v.at(i);        //当前需要插入的数
//        int j = i - 1;         //j为已排序序列的末下标
//        for ( ; j >= 0 && v.at(j) > key; --j) {
//            v.at(j + 1) = v.at(j);   //后移
//        }
//        print(v, -i);
//        v.at(j + 1) = key;        //插入到已排序序列中的合适位置
//        print(v, i);

	if (v.at(i) < v.at(i - 1)) {   //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
		int x = v.at(i);     //复制为哨兵,即存储待排序元素
        int j = i - 1;
		while (j >= 0 && x < v.at(j)) {   //查找在有序表的插入位置,还必须要保证j是>=0的 因为a[j]要合法
			v.at(j + 1) = v.at(j);
			j--;     //元素后移
		}
		v.at(j + 1) = x;     //插入到正确位置
	}
	print(v, i);      //打印每趟排序的结果
    }
}

//折半插入算法
int main() {
	vector<int> nums = { 8,9,1,4,2,3,6,7,5 };
	cin >> nums.back();
	BinaryLnsertionSort(nums);
};
void BinaryLnsertionSort(vector<int>& a) {
	int temp, i, j;//temp保存每一次的新数,因为数组元素的移动会覆盖掉新数
	int low, mid, high;//设置区间的两端和中间的下标
	for (i = 1; i < a.size(); ++i)//所谓外循环,每次放一个新数进去参与排序
	{
		if (a[i] < a[i - 1])//若下标i以前的数不是有序的,执行if语句内容
			//此时的无序指的是下标0~i-1是有序的,需要将下标i中的数重新插入,使其变得有序
		{
			temp = a[i];//保存每次的新数
			low = 0;
			high = i - 1;//每一轮第一次初始化low和high
			while (low <= high)
			{
				mid = (low + high) / 2;
				if (temp < a[mid])
					high = mid - 1;
				else
					low = mid + 1;
			}//while结束后,下标high+1处为待插入的位置
			for (j = i - 1; j > high; j--)
				a[j + 1] = a[j];
			a[high + 1] = temp;
			print(a, i-1);
		}
	}
}
//时间复杂度O(n^2) 空间复杂度O(1),稳定排序

void print(vector<int>& a, int i) {
    cout << "step" << i << ": ";
    //使用迭代器遍历
    //for (auto j = a.begin(); j != a.end(); ++j)
    //	cout << *j << " ";
    //使用下标遍历
    //for (size_t j = 0; j < a.size(); ++j)
    //	cout << a[j] << " ";
    //使用范围for语句
    for (auto& c : a)
        cout << c << " ";
    cout << endl;
}

冒泡排序

#include <iostream>
#include <vector>

using std::cout;
using std::endl;
using std::vector;
using std::swap;

void print(vector<int>& nums);
void bubbleSort(vector<int>& nums);

int main() {
    vector<int> nums = { 8,9,1,4,2,3,6,7,5,6 };
    bubbleSort(nums);
    print(nums);

    vector<int> n = { 1,2,3,4,8,7,6,9,10 };
    bubbleSort(n);
    print(n);

    return 0;
}
void print(vector<int>& nums) {
        static int i = 0;
        cout << "step" << i<<" ";
        for (auto i : nums) {
            cout << i << " ";
        }
        ++i;

}
void bubbleSort(vector<int>& nums) {
    int n = nums.size();
    bool flag = false;
    for (int i = 0; i < n - 1; ++i) {//i = 0 起,循环了n - 1趟,更符合规范理解
        flag = false;
        for (int j = 0; j < n - 1 - i; ++j) {
            if (nums[j] > nums[j + 1]) {
                //某一趟排序中,只要发生一次元素交换,flag就从false变为了true
                //也即表示这一趟排序还不能确定所剩待排序列是否已经有序,应继续下一趟循环
                //swap(nums[j], nums[j + 1]);
                nums[j] = nums[j] + nums[j + 1];
                nums[j + 1] = nums[j] - nums[j + 1];
                nums[j] = nums[j] - nums[j + 1];
                flag = true;
                print(nums);
                cout << endl;
            }
        }
        //但若某一趟中一次元素交换都没有,即依然为flag = false
        //那么表明所剩待排序列已经有序
        //不必再进行趟数比较,外层循环应该结束,即此时if (!flag) break; 跳出循环
        if (!flag) { break; }
    }
}
//时间复杂度O(n^2),空间O(1),原地排序,稳定排序
posted on 2025-03-01 23:14  非衣居士  阅读(38)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3