参考:https://blog.csdn.net/qq_35644234/category_6521704.html
浙大《数据结构》
数据结构:
第一章 绪论
什么是数据结构?
- 解决问题方法的效率,跟数据的组织方式有关
- 解决问题方法的效率,跟空间的利用效率有关
- 解决问题方法的效率,跟算法的巧妙程度有关
什么是算法?
- 一个有限指令集
- 接受一些输入(有的情况不需要输入)
- 产生输出
- 一定在有限步骤后终止
- 每一条指令必须:
- 有充分明确的目标,不可以有歧义
- 计算机能处理的范围之内
- 描述应不依赖于任何一种计算机语言以及具体的实现手段
什么是好的算法?
空间复杂度S(n)——根据算法写成的程序在执行时占用存储单元的长度。
- 这个长度往往与输入数据的规模有关。
- 空间复杂度过高的算法可能导致使用的内存超限,造成程序非正常中断。
时间复杂度T(n)——根据算法写成的程序在执行时耗费的时间的长度。
- 这个长度往往也与输入数据的规模有关。
- 时间复杂度过高的低效算法可能导致我们在有生之年都等不到运行结果
在分析一般算法的效率时,我们经常关注下面两种复杂度:
- 最坏情况复杂度Tworst(n)
- 平均复杂度Tavg(n)
- Tavg(n) <= Tworst(n)
复杂度分析小窍门
-
若两段算法分别有复杂度T1(n) = O(f1(n)) 和 T2(n) = O(f2(n)),则
- T1(n) + T2(n) = max( O(f1(n)), O(f2(n)) )
- T1(n) * T2(n) = O( f1(n) * f2(n) )
-
若T(n)是关于n的k阶多项式,那么T(n) = O(n^k) // 这里的O中间有一圆点
-
一个for循环的时间复杂度等于循环次数乘以循环体代码的复杂度
-
if-else结构的复杂度取决于if的条件判断复杂度和两个分支部分的复杂度,总体复杂度取三者中最大
第二章 线性表
引:多项式表示——链表结构存储非零项
链表中每个结点存储多项式中的一个非零项,包括系数和指数两个数据域以及一个指针域
typedef struct PolyNode *Polynomial;
struct PloyNode {
int coef;
int expon;
Polynomial link;
}
什么是线性表?
线性表(Linear List):由同类型数据元素构成有序序列的线性结构
- 表中元素个数称为线性表的 长度
- 线性表没有元素时,称为 空表
- 表起始位置称为 表头,表结束位置称为 表尾
线性表的顺序存储实现
利用数组的 连续存储空间顺序存放 线性表的各元素
主要操作的实现:
#include <iostream>
using namespace std;
// 扩充容量的步伐
#define sizestep 10
// 开始的容量大小
#define startsize 100
typedef int ElemType;
struct List {
ElemType* data; // 数据
int length; // 长度
int size; // 初始容量
};
// 创建一个空的线性表
void InitList(List& newList)
{
newList.size = startsize; // 初始容量为startsize;
newList.data = new ElemType[newList.size]; // 开辟空间
newList.length = 0; // 空表长度为0
}
// 销毁线性表
void DestroyList(List& newList)
{
newList.length = 0;
// 一定要先释放堆内存
delete[] newList.data;
// 释放堆内存后,并将对应的指针赋予nullptr是个良好的习惯
newList.data = nullptr;
}
// 清空线性表
void ClearList(List& newList)
{
newList.length = 0;
delete[] newList.data;
newList.data = nullptr;
// 重新为存放元素的变量开辟一个新的堆内存
newList.data = new ElemType[newList.size];
}
// 判断线性表是否为空
bool ListEmpty(List newList)
{
return newList.length;
}
// 返回线性表的长度
int ListLength(List newList)
{
return newList.length;
}
// 获取线性表上某个位置上的元素的值(位置从1开始计算)
void GetElem(List newList, int i, ElemType& e)
{
if (ListEmpty(newList)) {
cout << "线性表为空" << endl;
return;
}
if (i < 1 || i > newList.length) {
cout << "当前位置超出线性表范围" << endl;
return;
}
e = newList.data[i - 1];
}
// 获取元素的位置(这里直接返回该元素下标,而不是从1开始)
int LocationElem(List newList, ElemType e)
{
for (int i = 0; i < newList.length; ++i) {
if (newList.data[i] == e)
return i;
}
return -1;
}
// 获取前驱元素
void PriorElem(List newList, ElemType cur_e, ElemType& pre_e)
{
int location = 0;
location = LocationElem(newList, cur_e);
// 如果location为 -1,说明cur_e不在线性表中
if (location == -1) {
cout << cur_e << "不在线性表中" << endl;
return;
}
// 如果location为 0,说明cur_e在线性表的第一个位置,没有前一个元素
if (location == 0) {
cout << cur_e << "是线性表的第一个元素,没有前驱" << endl;
return;
}
pre_e = newList.data[location - 1];
}
// 获取后驱元素
void NextElem(List newList, ElemType cur_e, ElemType& next_e)
{
int location = 0;
location = LocationElem(newList, cur_e);
// 如果location为 -1,说明cur_e不在线性表中
if (location == -1) {
cout << cur_e << "不在线性表中" << endl;
return;
}
// 如果location为 newList.length - 1,说明cur_e在线性表的最后一个位置,没有后一个元素
if (location == newList.length - 1) {
cout << cur_e << "是线性表的最后一个元素,没有后驱" << endl;
return;
}
next_e = newList.data[location + 1];
}
// 向线性表中插入一个元素,需要判断插入位置的合法性(从1开始)
void InsertList(List& newList, int i, ElemType e)
{
// 插入位置不合法
if (i < 1 || i > newList.length + 1) {
cout << "请检查插入位置是否正确" << endl;
return;
}
int j = 0;
// 如果达到了线性表的最大容量,需要重新为线性表分配新的内存
if (newList.length == newList.size) {
// 先保存之前的内容
ElemType* p = new ElemType[newList.length];
for (j = 0; j < newList.length; ++j) {
p[j] = newList.data[j];
}
// 扩大容量
newList.size += sizestep;
delete[] newList.data;
// 重新分配内存
newList.data = new ElemType[newList.size];
// 恢复之前内容
for (j = 0; j < newList.length; ++j) {
newList.data[j] = p[j];
}
}
// 插入内容
for (int k = newList.length; k > i - 1; --k) {
newList.data[k] = newList.data[k - 1];
}
newList.data[i - 1] = e;
++newList.length;
}
// 删除一个元素(从1开始算起)
void DeleteList(List& newList, int i)
{
// 删除位置不合法
if (i < 1 || i > newList.length) {
cout << "请检查删除位置是否合法" << endl;
return;
}
for (int j = i - 1; j < newList.length; ++j) {
newList.data[j] = newList.data[j + 1];
}
--newList.length;
}
// 按照元素的值,删除对应元素的内容
// 通过传入参数,来决定删除第一个还是所有
// 0——删除第一个,1——删除所有
void Delete_dataList(List& newList, ElemType e, int order)
{
int flag = 0;
for (int i = 0; i < newList.length; ++i) {
if (newList.data[i] == e) {
flag = 1;
// 删除对应位置上的元素,而且i也要减少一个
DeleteList(newList, i + 1);
--i;
if (order == 0)
return;
}
}
if (flag == 1)
return;
cout << e << "不在线性表中" << endl;
}
// 链接两个线性表(当我们进行链接的时候,最好是希望两个链表有序)
void Connece_two_List(List a, List b, List& c)
{
// 对c进行一些数据初始化
c.length = c.size = a.length + b.length;
c.data = new ElemType[c.size];
// 采用指针的方式进行数据的移动,先把a和b数据第一个和最后一个元素的位置找出来
int i = 0;
int j = 0;
int k = 0;
while (i <= a.length - 1 && j <= b.length - 1) {
if (a.data[i] < b.data[j]) {
c.data[k++] = a.data[i++];
}
else if (a.data[i] > b.data[j])
c.data[k++] = b.data[j++];
else {
c.data[k++] = b.data[j++];
--c.length;
}
}
// 处理剩余一方的元素
while (i <= a.length - 1) {
c.data[k++] = a.data[i++];
}
while (j <= b.length - 1) {
c.data[k++] = b.data[j++];
}
}
// Print输入
void print(List& L)
{
for (int i = 0; i < L.length; ++i) {
cout << L.data[i] << " ";
}
cout << endl;
}
线性表的链式存储实现
不要求逻辑上相邻的两个元素物理上也相邻;通过“链”建立起数据元素之间的逻辑关系。
- 插入、删除不需要移动数据元素,只需要修改“链”。
主要操作的实现:
#include <iostream>
using namespace std;
typedef int ElemType;
// 保存长度的头结点,加入这个结点可以方便一些基本的操作
struct Node {
ElemType value;
Node* next; // 下一结点地址
};
// 创建一个空链表
void InitList(Node*& head)
{
head = new Node();
head->value = 0;
head->next = nullptr;
}
// 销毁单链表
void DestroyList(Node*& head)
{
Node* p;
while (head) {
p = head;
head = head->next;
delete p;
}
}
// 清空单链表
void ClearList(Node*& head)
{
Node* p;
while (head->next) {
p = head;
head = head->next;
delete p;
}
head->value = 0;
}
// 判断链表是否为空,为空返回true
bool ListEmpty(Node* head)
{
return head->value == 0;
}
// 返回链表的长度
bool ListLength(Node* head)
{
return head->value;
}
// 得到某个位置上的值
bool GetElem(Node* head, int i, ElemType& value)
{
// 想要得到的那个位置是否合法
if (i < 1 || i > head->value)
return false;
int j = 0;
Node* newhead = head;
while (j < i - 1) {
newhead = newhead->next;
++j;
}
value = newhead->next->value;
return true;
}
// 根据值得到该结点的地址以及序号
bool LocateElem(Node* head, ElemType value, Node*& address, int& order)
{
order = 1;
Node* temp = head->next;
while (temp && temp->value != value) {
temp = temp->next;
++order;
}
if (temp) {
address = temp;
return true;
}
else {
address = nullptr;
order = -1;
return false;
}
}
// 得到某个元素前面的那个元素的值
bool PriorElem(Node* newhead, ElemType cur_e, ElemType& pre_e)
{
while (newhead->next) {
if (newhead->next->value == cur_e) {
pre_e = newhead->value;
return true;
}
newhead = newhead->next;
}
return false;
}
// 得到某个元素的下一个元素
bool NextElem(Node* newhead, ElemType cur_e, ElemType& next_e)
{
newhead = newhead->next; // 从首元结点开始
while (newhead->next) {
if (newhead->value == cur_e) {
next_e = newhead->next->value;
return true;
}
newhead = newhead->next;
}
return false;
}
// 插入一个元素
bool InsertList(Node*& head, int i, ElemType value)
{
// 如果插入位置不合法,返回错误提示
if (i < 1 || i > head->value + 1)
return false;
// 得到插入位置的前一个结点
int j = 0;
Node* temp = head;
while (j < i - 1) {
temp = temp->next;
++j;
}
// s是一个临时结点
Node* s = new Node();
s->value = value;
s->next = temp->next;
temp->next = s;
++head->value;
return true;
}
// 根据下标的值进行删除,并返回删除信息
bool DeleteList(Node*& head, int i, ElemType& value)
{
// 如果删除位置不合法,返回错误提示
if (i < 1 || i > head->value)
return false;
// 先找到需要删除结点的前一个结点
int j = 0;
Node* temp = head;
while (j < i - 1) {
temp = temp->next;
++j;
}
//删除结点
Node* del = temp->next;
temp->next = del->next;
value = del->value;
delete del;
return true;
}
// 根据值进行删除,根据标志位删除 第一次出现/所有的该值
// flag = 0 删除第一次 flag = 1 删除所有的该值
bool Delete_data_List(Node*& head, ElemType value, int flag)
{
ElemType save = -1; // 临时保存要删除的数据
// 遍历整个线性表删除结点
int sign = 0; // 删除成功or失败
int j = 0;
Node* temp = head;
while (temp->next) {
temp = temp->next;
++j;
if (temp->value == value) {
DeleteList(head, j, save);
// 删除了一个,当前位置也会往前推一个
--j;
sign = 1; // 删除成功
if (flag == 0)
break;
}
}
if (sign == 0)
return false;
return true;
}
// 连接两个链表,默认已经排好序
void Connect_two_List(Node* a, Node* b, Node* c)
{
c->value = a->value + b->value;
int a_value = a->value;
int b_value = b->value;
int i = 1;
int j = 1;
int k = 1;
Node* a_temp = a->next;
Node* b_temp = b->next;
while (i <= a_value && j <= b_value) {
if (a_temp->value < b_temp->value) {
++i;
InsertList(c, k, a_temp->value);
++k;
a_temp = a_temp->next;
}
else if (a_temp->value > b_temp->value) {
++j;
InsertList(c, k, b_temp->value);
++k;
b_temp = b_temp->next;
}
else {
++j;
++i;
InsertList(c, k, a_temp->value);
--c->value;
++k;
a_temp = a_temp->next;
b_temp = b_temp->next;
}
}
while (i <= a_value) {
++i;
InsertList(c, k, a_temp->value);
++k;
a_temp = a_temp->next;
}
while (j <= b_value) {
++i;
InsertList(c, k, b_temp->value);
++k;
b_temp = b_temp->next;
}
}
广义表与多重链表
什么是广义表?
- 广义表是线性表的推广
- 对于线性表而言,n个元素都是基本的 单元素;
- 广义表中,这些元素不仅可以是单元素也可以是 另一个广义表
typedef struct GNode* GList;
struct GNode {
int Tag; // 标志域:0表示结点是单元素,1表示结点是广义表
union { // 子表指针域SubList与单元素数据域Data复用,即共用存储空间
ElementType Data;
GList SubList;
}URegion;
GList Next; // 指向后继结点
}
什么是多重链表?
链表中的节点可能同时隶属于多个链
- 多重链表中结点的 指针域会有多个,如前面的Next和SubList两个指针域
- 但包含两个指针域的链表并不一定是多重链表,比如 双向链表不是多重链表。
多重链表有广泛的用途:如树、图这样相对复杂的数据结构都可以采用多重链表方式实现存储。
第三章 栈
堆栈
具有一定操作约束的线性表
- 只在一端(栈顶,Top)做 插入、删除
- 插入数据:入栈(Push)
- 删除数据:出栈(Pop)
- 后入先出:Last In First Out(LIFO)
堆栈的顺序存储实现
栈的顺序存储结构通常由一个一维数组和一个记录 栈顶 元素位置的变量组成。
主要操作的实现:
#include <iostream>
using namespace std;
/*
* 暂时没有写扩充容量的功能
* 后期完善时需注意
*/
// 扩充容量的步伐
#define sizestep 10
// 开始的容量大小
#define startsize 100
typedef int ElemType;
struct SeqStack {
ElemType* data; // 数据
int top; // 用于栈顶指针
int size; // 初始容量
};
// 顺序栈初始化,成功返回栈对象指针,失败返回空指针nullptr
void InitSeqStack(SeqStack& s)
{
s.size = startsize;
s.data = new ElemType[s.size];
s.top = -1;
if (!s.data) {
cout << "空间不足" << endl;
exit(-1);
}
}
// 判断栈是否为空
bool isEmptySeqStack(SeqStack& s)
{
return (s.top == -1) ? true : false;
}
// 入栈
bool pushSeqStack(SeqStack& s, ElemType e)
{
if (s.top == s.size - 1) {
return false;
}
s.data[++s.top] = e;
return true;
}
// 出栈
bool popSeqStack(SeqStack& s, ElemType& e)
{
if (s.top == -1) // if (isEmptySeqStack(s))
return false;
else {
e = s.data[s.top--];
return true;
}
}
// 取栈顶元素
bool topSeqStack(SeqStack s, ElemType& e)
{
if (s.top == -1)
return false; // 栈空
else {
e = s.data[s.top];
return true;
}
}
// 清空栈
void clearSeqStack(SeqStack& s)
{
s.top = -1;
delete[] s.data;
s.data = nullptr;
// 重新分配堆内存
s.data = new ElemType[s.size];
}
// 销毁栈
void destroySeqStack(SeqStack& s)
{
// 归零
s.size = 0;
s.top = -1;
delete[] s.data;
s.data = nullptr;
}
// 实现两栈共享空间
struct DoubleSeqStack {
ElemType* data;
int top1;
int top2;
};
void initDoubleSeqStack(DoubleSeqStack& s)
{
s.data = new ElemType[startsize];
s.top1 = -1;
s.top2 = startsize;
}
// 入栈,根据flag选择 栈1 还是 栈2
bool pushDoubleSeqStack(DoubleSeqStack& s, ElemType e, int flag)
{
// 栈满
if (s.top1 + 1 == s.top2)
return false;
if (flag == 1) {
s.data[++s.top1] = e;
}
else if (flag == 2) {
s.data[--s.top2] = e;
}
else {
cout << "flag有误" << endl;
return false;
}
return true;
}
// 出栈,根据flag选择 栈1 还是 栈2
bool popDoubleSeqStack(DoubleSeqStack& s, ElemType& e, int flag)
{
if (flag == 1) {
if (s.top1 == -1)
return false;
e = s.data[s.top1--];
}
else if (flag == 2) {
if (s.top2 == startsize)
return false;
e = s.data[s.top2++];
}
else {
cout << "flag有误" << endl;
return false;
}
return true;
}
例:用一个数组实现两个堆栈,要求最大地利用数组空间,使数组只要有空间入栈操作就可以成功。
#define startsize 100
typedef int ElemType;
// 实现两栈共享空间
struct DoubleSeqStack {
ElemType* data;
int top1;
int top2;
};
void initDoubleSeqStack(DoubleSeqStack& s)
{
s.data = new ElemType[startsize];
s.top1 = -1;
s.top2 = startsize;
}
// 入栈,根据flag选择 栈1 还是 栈2
bool pushDoubleSeqStack(DoubleSeqStack& s, ElemType e, int flag)
{
// 栈满
if (s.top1 + 1 == s.top2)
return false;
if (flag == 1) {
s.data[++s.top1] = e;
}
else if (flag == 2) {
s.data[--s.top2] = e;
}
else {
cout << "flag有误" << endl;
return false;
}
return true;
}
// 出栈,根据flag选择 栈1 还是 栈2
bool popDoubleSeqStack(DoubleSeqStack& s, ElemType& e, int flag)
{
if (flag == 1) {
if (s.top1 == -1)
return false;
e = s.data[s.top1--];
}
else if (flag == 2) {
if (s.top2 == startsize)
return false;
e = s.data[s.top2++];
}
else {
cout << "flag有误" << endl;
return false;
}
return true;
}
堆栈的链式存储实现
栈的链式存储结构实际上就是一个单链表,叫做 链栈。插入和删除操作只能在链栈的栈顶进行。
栈顶指针Top应该在链表的哪一头?
以链表为底层的数据结构时,以链表头为栈顶,便于节点的插入与删除,压栈产生的新节点将一直出现在链表的头部
主要操作的实现:
#include <iostream>
using namespace std;
typedef int ElemType;
// 链栈结点
struct StackNode {
ElemType value;
StackNode* next;
};
// 链栈结构
struct LinkStack {
StackNode* top;
int length;
};
// 初始化
void initStack(LinkStack& s)
{
s.top = nullptr;
s.length = 0;
}
// 判断栈是否为空
bool isEmptyStack(LinkStack s)
{
return (!s.top) ? true : false;
}
// 入栈
bool pushStack(LinkStack s, ElemType e)
{
StackNode* temp = new StackNode;
// 空间申请失败
if (!temp) {
return false;
}
temp->value = e;
temp->next = s.top;
// 将新元素作为栈顶指针
s.top = temp;
++s.length;
return true;
}
// 出栈
bool popStack(LinkStack& s, ElemType& e)
{
// 空栈
if (!s.top)
return false;
e = s.top->value;
StackNode* temp = s.top;
s.top = s.top->next;
--s.length;
delete temp;
return true;
}
// 获取栈顶元素
bool GetTop(LinkStack s, ElemType& e)
{
if (!s.top)
return false;
e = s.top->value;
return true;
}
例:表达式求值
中缀表达式如何转换为后缀表达式:
从头到尾读取中缀表达式的每个对象,对不同对象按不同的情况处理。
- 运算数:直接输出
- 左括号:压入堆栈
- 右括号:将栈顶的运算符弹出并输出,直到遇到左括号(出栈,不输出)
- 运算符:
- 若优先级大于栈顶运算符时,则把它压栈
- 若优先级小于栈顶运算符时,将栈顶运算符弹出并输出;再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈
- 若各对象处理完毕,则把堆栈中存留的运算符一并输出
堆栈的其他引用
- 函数调用及递归实现
- 深度优先搜索
- 回溯算法
第四章 队列
什么是队列?
具有一定操作约束的线性表
- 插入和删除操作:只能在一端插入,而在另一端删除
- 数据插入:入队
- 数据删除:出队
- 先来先服务
- 先进先出:FIFO
队列的顺序存储实现
队列的顺序存储结构通常由一个 一维数组 和一个记录队列头元素位置的变量 front以及一个记录队尾元素位置的变量 rear组成。
循环队列
堆栈空和满的判别条件是什么?
解决方案:
- 设置一个标志变量flag,当
front == rear
,且flag = 0
时为队列空;当front == rear
,且flag = 1
时为队列满。 - 当队列为空时,条件是
front == rear
;当队列满时,修改条件,保留一个元素空间front=(rear+1)%MAXSIZE
。
主要操作的实现:
#include <iostream>
using namespace std;
#define MAXSIZE 100
typedef int ElemType;
struct SeqQueue {
ElemType* data;
int front;
int rear;
};
// 初始化
void initSeqQueue(SeqQueue& Q)
{
Q.data = new ElemType[MAXSIZE];
if (!Q.data) {
exit(-1);
}
Q.front = 0;
Q.rear = 0;
}
// 入队
bool enqueue(SeqQueue& Q, ElemType e)
{
if ((Q.rear + 1) % MAXSIZE == Q.front) {
return false;
}
Q.data[Q.rear] = e; // 将e赋值给队尾
Q.rear = (Q.rear + 1) % MAXSIZE;
return true;
}
// 出队
bool dequeue(SeqQueue& Q, ElemType& e)
{
if (Q.front == Q.rear) {
return false;
}
e = Q.data[Q.front];
Q.front = (Q.front + 1) % MAXSIZE;
return true;
}
// 求队长
int lengthSeqQueue(SeqQueue Q)
{
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
// 取队头元素
bool GetHead(SeqQueue Q, ElemType& e)
{
if (Q.front == Q.rear) {
return false;
}
else {
e = Q.data[Q.front];
return true;
}
}
队列的链式存储实现
队列的链式存储结构也可以用一个 单链表 实现。插入和删除操作分别在链表的两头进行;队列指针front和rear应该分别指向链表的哪一头?
front 指向链表的头,rear指向链表的尾
注意:下面的队列中有一个头结点
主要操作的实现:
#include <iostream>
using namespace std;
typedef int ElemType;
struct QNode {
ElemType data;
QNode* next;
};
struct LinkQueue {
QNode* front; // 队头指针
QNode* rear; // 队尾指针
};
// 初始化
void initQueue(LinkQueue& Q)
{
// front和rear指向头结点,头结点不存储元素
Q.front = Q.rear = new QNode;
if (!Q.front) {
exit(-1);
}
Q.front->next = nullptr;
}
// 判断队是否为空
bool isEmptyQueue(LinkQueue& Q)
{
return (!Q.rear->next) ? true : false;
}
// 取队头元素
bool GetHeadQueue(LinkQueue& Q)
{
if (Q.front == Q.rear) {
return false;
}
else {
return Q.front->next->data;
}
}
// 入队
bool enqueue(LinkQueue& Q, ElemType e)
{
QNode* temp = new QNode;
if (!temp) {
return false;
}
temp->data = e;
temp->next = nullptr;
Q.rear->next = temp;
Q.rear = temp;
return true;
}
// 出队
bool dequeue(LinkQueue& Q, ElemType& e)
{
if (Q.front == Q.rear) {
return false;
}
QNode* temp = Q.front->next; // 临时指针指向首元结点
e = temp->data;
Q.front->next = temp->next;
if (Q.rear == temp)
Q.rear = Q.front;
delete temp;
}
// 销毁队列
void destroyQueue(LinkQueue& Q)
{
while (Q.front) {
Q.rear = Q.front->next;
delete Q.front;
Q.front = Q.rear;
}
Q.rear = Q.front = nullptr;
}
例:多项式的加减法运算实现