目录
(3).顺序表的插入与删除(以静态分配为例)(示例代码中包含了一下必要的基本函数)
1. 基本概念
线性表:
(1).其中的各个元素,数据类型相同。
(2).元素之间,有次序。
(3).都有表头元素和表尾元素。
(4).除了表头表尾,每个元素都可以找到一个直接前驱和直接后继。
2.线性表的基本操作
表的从无到有
InitList(&L):初始化一个线性表
DestroyList(&L):销毁
。
ListInsert(&L, i, e):插入,在表L的第 i 处插入元素 e
ListDelete(&L, i, &e):删除,将表L的第 i 处元素删除,并用 e 返回该被删除的元素
。
LocateElem(L, e):按值查找,在表中查找特定关键字e 的元素,返回的是e 的位序,而非下标
GetElem(L, i):按位查找,获取表中第 i 个元素的值
。
其他常规操作:
Length(L):求表长度
PrintList(L):输出表的所有元素
Empty(L):判断表是否为空,返回true or false
高度概括,即为,创销与增删改查
3.顺序表
以顺序存储方式储存数据的线性表
(1).静态分配
#define MAX 10
//顺序表(静态分配)
class SqList
{
public:
int data[MAX];
int length;
};
//初始化
void InitList(SqList &l)
{
for(int i = 0 ;i < 10 ;i++)
{
l.data[i] = 0;
}
l.length = 0;
}
//打印所有元素
void PrintList(SqList &l)
{
for (int i = 0; i < 10; i++)
cout << "第" << i << "个数:" << l.data[i] << endl;
}
//测验
void test01()
{
SqList l;
InitList(l);
PrintList(l);
}
(2).动态分配
#define InitSize 10
//顺序表(动态分配)
class SqList
{
public:
int* data; //指示动态分配数组的指针
int MaxSize; //指示最大容量
int length; //指示当前长度
};
//初始化顺序表
void InitList(SqList& l)
{
l.data = new int[InitSize];
l.MaxSize = InitSize;
l.length = 0;
for (int i = 0; i < l.MaxSize; i++)
{
l.data[i] = 0;
}
}
//增长数组空间
void IncreaseSize(SqList& l, int len)
{
int* p = l.data; //暂存原数组中的数据
l.data = new int[10 + len]; //扩展新的数组
for (int i = 0; i < l.length; i++) //将原数据拷贝进新数组中
{
l.data[i] = p[i];
}
l.MaxSize = InitSize + len; //修改数组的状态数据
delete p; //将p释放
}
//打印所有元素
void PrintList(SqList& l)
{
for (int i = 0; i < 10; i++)
cout << "第" << i << "个数:" << l.data[i] << endl;
}
void test01()
{
SqList l;
InitList(l);
PrintList(l);
}
(3).顺序表的插入与删除(以静态分配为例)(示例代码中包含了一下必要的基本函数)
//插入
bool ListInsert(SqList& l, int d, int e)
{
if (l.length >= MAX) //首先要判断表是否已满、插入是否合法
{
cout << "插入失败,已达上限" << endl;
return false;
}
if (d < 1 || d > l.length + 1)
{
cout << "插入失败,无直接前驱" << endl;
return false;
}
for (int j = l.length; j >= d; j--) //将插入点之后的元素后移
l.data[j] = l.data[j - 1];
l.data[d - 1] = e; //插入,因为d指的是第几个数,在数组的换算中要减一
l.length++;
return true;
}//删除
bool ListDelete(SqList& l, int d, int &e)
{
if (d < 1 || d >l.length) //判断删除的位置是否合法
return false;
e = l.data[d - 1]; //暂存删除掉的元素
for (int j = d; j < l.length; j++) //将被删除元素之后的元素前移
l.data[j - 1] = l.data[j]; //此处,必须是j = d,j-1被j覆盖,若j = d-1,则下文的覆盖会变为j 被j+1 覆盖,而j+1在最后有可能会超过数组的最大容量
l.length--;
return true;
}
示例代码
#define MAX 10
//顺序表(静态分配)
class SqList
{
public:
int data[MAX];
int length;
};
//初始化
void InitList(SqList& l)
{
for (int i = 0; i < 10; i++)
{
l.data[i] = 0;
}
l.length = 0;
}
//打印所有元素
void PrintList(SqList& l)
{
for (int i = 0; i < 10; i++)
cout << "第" << i << "个数:" << l.data[i] << endl;
}
//存入数据
void InputElem(SqList& l, int e)
{
int i = 0;
while (i < MAX)
{
if (l.data[i] == 0)
{
l.data[i] = e;
l.length++;
break;
}
i++;
}
}
//获取顺序表长度
int GetLength(SqList l)
{
//cout << l.length << endl;
return l.length;
}
//插入
bool ListInsert(SqList& l, int d, int e)
{
if (l.length >= MAX) //首先要判断表是否已满、插入是否合法
{
cout << "插入失败,已达上限" << endl;
return false;
}
if (d < 1 || d > l.length + 1)
{
cout << "插入失败,无直接前驱" << endl;
return false;
}
for (int j = l.length; j >= d; j--) //将插入点之后的元素后移
l.data[j] = l.data[j - 1];
l.data[d - 1] = e; //插入,因为d指的是第几个数,在数组的换算中要减一
l.length++;
return true;
}
//删除
bool ListDelete(SqList& l, int d, int &e)
{
if (d < 1 || d >l.length) //判断删除的位置是否合法
return false;
e = l.data[d - 1]; //暂存删除掉的元素
for (int j = d; j < l.length; j++) //将被删除元素之后的元素前移
l.data[j - 1] = l.data[j]; //此处,必须是j = d,j-1被j覆盖,若j = d-1,则下文的覆盖会变为j 被j+1 覆盖,而j+1在最后有可能会超过数组的最大容量
l.length--;
return true;
}
//查看情况
void CheckList(SqList& l)
{
PrintList(l);
cout << "当前长度为" << GetLength(l) << endl;
}
//测验
void test01()
{
SqList l;
InitList(l);
//输入部分数据
InputElem(l, 1);
InputElem(l, 2);
InputElem(l, 3);
InputElem(l, 4);
CheckList(l);
//开始插入
if(ListInsert(l, 3, 6))
CheckList(l);
//开始删除
int a = -1;
if (ListDelete(l, 2, a))
CheckList(l);
}
(4).按位序查找、按值查找
很简单,不多赘述
//判断d的合法性
bool JugdeD(SqList l, int d)
{
if (d < 1 || d > l.length)
return false;
return true;
}
//按位序查找
int GetElem(SqList l, int d)
{
if (JugdeD(l, d))
return l.data[d - 1];
return 0;
}
//按值查找
int LocateElem(SqList l, int e)
{
for (int i = 0; i < l.length; i++)
{
if (l.data[i] == e) //数组储存的数据,若是类等复杂的数据类型,则需要对等号进行重载
return i + 1;
}
return 0;
}
//其余代码与上文相同
//其中,JugdeD函数可以替换上文插入与删除中对位序合法性的判别————封装
4.链表
以链式存储方式储存数据的线性表
(1).单链表
i.单链表(带头结点)的定义
//单链表
class LNode
{
public:
int data; //数据域,存放数据
LNode* next; //指针域,指向下一个节点
};
//用using关键字给类起别名,用LinkList指代的是头结点,代表的是整个链表
using LinkList = LNode*;
//初始化
bool InitList(LinkList& L)
{
L = new LNode();
if (L == nullptr) //如果成立,则说明内存不足,分配失败
return false;
L->next = nullptr;
return true;
}
ii.插入、删除(带头结点)
不带头结点的,要注意头指针的变动,其他的都雷同。
插入(普通版)
//插入
bool ListInsert(LinkList& L, int i, int e)
{
if (i < 1) //判断插入位点是否合法[1]——i值的合法性
{
cout << "i为负数" << endl;
return false;
}
LNode* p = L; //让p与L指向相同的位点,L是指示头指针的,所以L是不能改变的
LNode* s = new LNode(); //新的数据储存
s->data = e;
while (p != nullptr && i != 1) //由头结点起始,开始遍历寻找对应位点
{
p = p->next;
i--;
}
if (p == nullptr) //判断插入的位点是否合法[2]——i值对应的节点的合法性
{
cout << "插入位点超出实际长度" << endl;
return false;
}
s->next = p->next; //开始接轨,顺序不能乱
p->next = s;
return true;
}
插入(封装版)
//特定节点的后插操作
bool InsertNextNode(LNode* p, int e)
{
if (p == nullptr)
{
cout << "插入位点超出实际长度" << endl;
return false;
}
LNode* s = new LNode();
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//插入
bool ListInsert(LinkList& L, int i, int e)
{
if (i < 1) //判断插入位点是否合法[1]——i值的合法性
{
cout << "i为负数" << endl;
return false;
}
LNode* p = L; //让p与L指向相同的位点,L是指示头指针的,所以L是不能改变的
while (p != nullptr && i != 1) //由头结点起始,开始遍历寻找对应位点
{
p = p->next;
i--;
}
return InsertNextNode(p, e); //被封装了的部分
}
删除
//按位查找,获得第i个结点(此函数可以将上文的检索节点的部分替换)
LNode* GetElem(LinkList& L, int i)
{
if (i < 0)
return nullptr;
LNode* p = L;
while (p != nullptr && i != 0)
{
p = p->next;
i--;
}
return p;
}
//删除节点
bool DeleteNode(LNode* p)
{
if (p->next == nullptr)
{
free(p);
return true;
}
else
{
LNode* q = p->next; //用下一个节点q 将被删除节点p 覆盖,使p,q成为相同的节点,之后释放下一个节点
p->data = p->next->data;
p->next = q->next;
free(q);
return true;
}
}
//删除:删除指定节点,并保留删除的数据
bool ListDelete(LinkList& L, int i, int& e)
{
if (i < 1) //判断删除的位置是否合法
return false;
LNode* p = GetElem(L, i - 1);
LNode* q = GetElem(L, i); //指向被删除的节点
e = q->data; //储存被删除的数据
p->next = q->next; //绕过被删除节点,并释放被删除节点
free(q);
return true;
}
iii.查找
其代码逻辑在“插入、删除”中已经有所体现
//按位查找,获得第i个结点
LNode* GetElem(LinkList& L, int i)
{
if (i < 0)
return nullptr;
LNode* p = L;
while (p != nullptr && i != 0)
{
p = p->next;
i--;
}
return p;
}
//按值查找
LNode* LocateElem(LinkList L, int e)
{
LNode* p = L->next;
while (p != nullptr && p->data != e)
{
p = p->next;
}
return p;
}
iiii.单链表的建立
建立一个单链表主要有两步:
Step 1:初始化
Step 2:将数据元素,存入链表(插入)
初始化的函数在“定义”中已经给出
Step 2 中的存入方法主要有两种:尾插法、头插法。
尾插法
//尾插法建立单链表
LinkList List_TailInsert(LinkList& L)
{
int x;
InitList(L);
LNode* s, * r = L; //创建一套表尾指针
cin >> x;
while (x != 9999) //设立一个截止输入,输入9999时跳出循环
{
s = new LNode(); //用s 建立新节点
s->data = x;
r->next = s; //通过r 指针让节点指向下一个节点
r = s;
cin >> x;
}
cout << "输入循环已跳出" << endl;
r -> next = nullptr;
return L;
}
头插法
//头插法建立单链表
LinkList List_HeadInsert(LinkList& L)
{
int x;
InitList(L);
LNode* s;
cin >> x;
while (x != 9999) //设立一个截止输入,输入到9999时跳出循环
{
s = new LNode(); //用s 建立新节点
s->data = x;
s->next = L->next; //
L->next = s;
cin >> x; //使用此法,亦可将尾插得到的链表,逆置(反过来)
}
cout << "输入循环已跳出" << endl;
return L;
}
iiiii.完整的代码
#include<iostream>
#include<string>
using namespace std;
//单链表
class LNode
{
public:
int data; //数据域,存放数据
LNode* next; //指针域,指向下一个节点
};
//用using关键字给类起别名,用LinkList指代的是头结点,代表的是整个链表
using LinkList = LNode*;
//初始化
bool InitList(LinkList& L)
{
L = new LNode();
if (L == nullptr) //如果成立,则说明内存不足,分配失败
return false;
L->next = nullptr;
return true;
}
//尾插法建立单链表
LinkList List_TailInsert(LinkList& L)
{
int x;
InitList(L);
LNode* s, * r = L; //创建一套表尾指针
cin >> x;
while (x != 9999) //设立一个截止输入,输入到9999时跳出循环
{
s = new LNode(); //用s 建立新节点
s->data = x;
r->next = s; //通过r 指针让节点指向下一个节点
r = s;
cin >> x;
}
cout << "输入循环已跳出" << endl;
r -> next = nullptr;
return L;
}
//头插法建立单链表
LinkList List_HeadInsert(LinkList& L)
{
int x;
InitList(L);
LNode* s;
cin >> x;
while (x != 9999) //设立一个截止输入,输入到9999时跳出循环
{
s = new LNode(); //用s 建立新节点
s->data = x;
s->next = L->next; //
L->next = s;
cin >> x; //使用此法,亦可将尾插得到的链表,逆置(反过来)
}
cout << "输入循环已跳出" << endl;
return L;
}
//打印所有元素
void PrintList(LinkList& L)
{
LNode* p = L;
int i = 0;
while (p != nullptr)
{
cout << "第" << i << "个节点的数据为" << p->data << endl;
p = p->next;
i++;
}
cout << "______________________________" << endl;
}
//存入数据(单纯的往后接数据)
void InputElem(LinkList& L, int e)
{
LNode* p = new LNode();
int i = 0;
LNode* q = L;
p->data = e;
p->next = nullptr;
while (1)
{
if (q->next != nullptr)
{
q = q->next;
}
else
{
q->next = p;
break;
}
}
}
//后插操作:在特定节点之后插入节点
bool InsertNextNode(LNode* p, int e)
{
if (p == nullptr)
{
cout << "插入位点超出实际长度" << endl;
return false;
}
LNode* s = new LNode();
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//前插操作:在特定节点之前插入节点
bool InsertPriorNode(LNode* p, int e)
{
if (p == nullptr) //判别传入的p是否合法
{
cout << "插入位置不合法" << endl;
return false;
}
LNode* s = new LNode();
if (s == nullptr) //判别内存是否分配成功,其实,每一个分配内存的变量后面,都应该判别一下是否分配成功
return false;
s->data = p->data; //将p复制给s,让s 成为原来的p 节点
s->next = p->next;
p->next = s; //让原来的p节点作为现在前插的节点
p->data = e;
return true;
}
//插入
bool ListInsert(LinkList& L, int i, int e)
{
if (i < 1) //判断插入位点是否合法[1]——i值的合法性
{
cout << "i为负数" << endl;
return false;
}
LNode* p = L; //让p与L指向相同的位点,L是指示头指针的,所以L是不能改变的
while (p != nullptr && i != 1) //由头结点起始,开始遍历寻找对应位点
{
p = p->next;
i--;
}
return InsertNextNode(p, e);
}
//按位查找,获得第i个结点
LNode* GetElem(LinkList& L, int i)
{
if (i < 0)
return nullptr;
LNode* p = L;
while (p != nullptr && i != 0)
{
p = p->next;
i--;
}
return p;
}
//按值查找
LNode* LocateElem(LinkList L, int e)
{
LNode* p = L->next;
while (p != nullptr && p->data != e)
{
p = p->next;
}
return p;
}
//删除节点
bool DeleteNode(LNode* p)
{
if (p->next == nullptr)
{
free(p);
return true;
}
else
{
LNode* q = p->next; //用下一个节点q 将被删除节点p 覆盖,使p,q成为相同的节点,之后释放下一个节点
p->data = p->next->data;
p->next = q->next;
free(q);
return true;
}
}
//删除:删除指定节点,并保留删除的数据
bool ListDelete(LinkList& L, int i, int& e)
{
if (i < 1) //判断删除的位置是否合法
return false;
LNode* p = GetElem(L, i - 1);
LNode* q = GetElem(L, i); //指向被删除的节点
e = q->data; //储存被删除的数据
p->next = q->next; //绕过被删除节点,并释放被删除节点
free(q);
return true;
}
//获取顺序表长度
int GetLength(LinkList L)
{
LNode* p = L;
int cout = 0;
p = p->next; //因为有头结点,所以才有此行代码
while (p != nullptr)
{
p = p->next;
cout++;
}
return cout;
}
//测验
void test01()
{
//用尾插法建立链表
LinkList L;
List_TailInsert(L);
//List_HeadInsert(L);
//检查数据
PrintList(L);
//插入
ListInsert(L, 1, 999);
ListInsert(L, 3, 888);
PrintList(L);
//删除
int a = 0;
ListDelete(L, 2, a);
PrintList(L);
cout << "a = " << a << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
(2).双链表
双链表和单链表的本质区别就是:双链表可以向前检索,单链表只能向后检索。
i.双链表的初始化
//双链表
class DNode
{
public:
int data; //数据域
DNode* prior,* next; //指针域,指向前驱与后继
};
using DLinkList = DNode*;
//初始化
bool InitDList(DLinkList& L)
{
L = new DNode();
if (L == nullptr) //如果成立,则说明内存不足,分配失败
return false;
L->prior = nullptr; //头结点的prior 永远指向NULL
L->next = nullptr;
return true;
}
ii.插入、删除
插入
//在p结点后插入s结点
bool InsertNextDNode(DNode* p, DNode* s)
{
if (p == nullptr || s == nullptr) //如果p 和s 中有任何一个是空的,那么插入都是没有意义的
return false;
s->next = p->next; //先接入后继,再接入前驱,因为先接入前驱的话,会失去后继
if (p->next != nullptr) //双链表的特性,如果p 的后继不为空,就要考虑原本p 的后继的前驱
p->next->prior = s;
s->prior = p; //再接入前驱
p->next = s; //最后才修改前驱的后继
return true;
}
删除
//删除p结点的后继结点
bool DeleteNextDNode(DNode* p)
{
if (p == nullptr) //判断所谓的p结点是否合法
return false;
DNode* q = p->next; //设置一个指针指向后继结点,借此对后继结点进行操作
if (q != nullptr) //如果判断q == NULL,则需要写两次return true;会略显冗余
p->next = q->next;
delete q;
return true;
}
//销毁双链表
bool DestoryList(DLinkList& L)
{
while (L->next != nullptr)
DeleteNextDNode(L);
delete L;
L = nullptr; //头指针最后要置空
}
iii.双链表的遍历
//向后遍历
while(p != NULL)
{
//常用于打印链表、计算链表长度等
p = p->next;
...
}
/
//向前遍历
while(p != NULL)
{
p = p->prior;
...
}
/
//向前遍历(不含头结点)
while(p->prior != NULL)
{
//最后会停在第一个有内容的结点处
p = p->prior;
...
}
(3).循环链表
分为循环单链表和循环双链表,与普通链表的区别就在于最后一个结点会指向头结点,而非指向空。
i.循环单链表
//循环单链表
class LNode
{
public:
int data;
LNode* next;
};
using LinkList = LNode*;
//初始化
bool InitList(LinkList& L)
{
L = new LNode();
if (L == nullptr)
return false;
L->next = L; //与单链表的区别
return true;
}
//判断是否为空
bool Empty(LinkList L)
{
if (L->next == L) //与单链表的区别
return true;
else
return false;
}
//判断p是否在表尾
bool isTail(LinkList L, LNode* p)
{
if (p->next == L) //与单链表的区别
return true;
else
return false;
}
//插入与删除,则可直接通过循环进行任意位置的操作
注:如果需要频繁地对表头和表尾进行操作时,可选用循环链表,设置一个指针指向表尾,即可实现最省时的表头表尾操作。
ii.循环双链表
表头的前驱是表尾
表尾的后继是表头
//循环双链表
class DNode
{
public:
int data; //数据域
DNode* prior, * next; //指针域
};
using DLinkList = DNode*;
//初始化
bool InitDList(DLinkList& L)
{
L = new DNode();
if (L == nullptr)
return false;
L->prior = L;
L->next = L;
return true;
}
//Empty函数、isTail函数与循环单链表相同
//在p结点后插入s结点(无需考虑结尾是否为空的问题)
bool InsertNextDNode(DNode* p, DNode* s)
{
if (p == nullptr || s == nullptr)
return false;
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
//删除p结点的后继结点(无需考虑结尾是否为空的问题)
bool DeleteNextDNode(DNode* p)
{
if (p == nullptr)
return false;
DNode* q = p->next;
p->next = q->next;
delete q;
return true;
}
(4).静态链表
#define MaxSize 10 //
//静态链表
class Node
{
public:
int data;
int next;
};
using SLinkList = Node[MaxSize];
结点中,有两个元素,一个储存数据,一个储存数组下标.
末尾结点中的数组下标标记为-1,
静态,指的就是链表可以储存的数据数量是固定的,
在定义和删除时,为了防止脏数据(内存中原本储存的未知数据)等的干扰,要让空闲节点的数组下标为某个特殊值,如-2.
不常用