数据结构
文章目录
- 数据结构
- 第2章 线性表
- 第3章 栈和队列
- 第4章 串
- 第5章 数组和广义表
- 第6章 树
- 一、二叉树
- (一)二叉树
- 1、定义
- 2、性质
- (1)在二叉树的第 i ( i ≥ 1 ) i(i≥1) i(i≥1)层上至多有 2 i − 1 2^i-1 2i−1个结点
- (2)深度为 k k k的二叉树最多有 2 k − 1 2^k-1 2k−1个结点
- (3)对==任意二叉树==T,如果其终端结点数为 n 0 n_0 n0(度数为0的结点数), n 1 n_1 n1, n 2 n_2 n2分别表示度数为1,2的结点个数,则 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
- (4)具有n个结点的==完全二叉树==的深度为 ⌊ l o g 2 n ⌋ + 1 ⌊log_2n⌋+1 ⌊log2n⌋+1或者( ⌈ l o g 2 ( n + 1 ) ⌉ ⌈log2(n+1)⌉ ⌈log2(n+1)⌉)。
- (5)对于一个具有n个结点的==完全二叉树==,其结点按层序编号(从第1层到第 ⌊ l o g 2 n ⌋ + 1 ⌊log_2n⌋+1 ⌊log2n⌋+1层,每层从左到右),则对任一结点 k i ( 1 ≤ i ≤ n ) k_i(1≤i≤n) ki(1≤i≤n),有
- 3、存储结构
- (二)遍历二叉树
- (三)线索二叉树
- 二、树和森林
- 三、赫夫曼树及其应用
- 第7章 图
- 第8章 查找
- 第9章 内部排序
第2章 线性表
一、顺序表
1、头文件
#include <iostream>
#include <stdlib.h>
using namespace std;
//---动态---↓
#define LIST_INIT_SIZE 80
#define LISTINCREMENT 10
2、顺序表示的实现
(1)数据元素的数据类型
//顺序表数据类型的实现
typedef struct
{
char name[8];//姓名
// int age;//年龄
// char sex[2];//性别
float score;//分数
}ElemType;
(2)顺序表的数据类型
①动态
//顺序表的实现——动态数组
typedef struct
{
ElemType* elem;//存储空间基址
int length;//当前长度
int listsize;//当前分配的存储容量
}SqList;
②静态
typedef struct
{
ElemType data[30];
int length;
int listsize;
}SqList;
(3)顺序表的实现
①动态
SqList L;
//第i个人的信息存储在:
L.elem[i-1].name;//姓名
L.elem[i-1].age;//年龄
L.elem[i-1].sex;//性别
L.elem[i-1].score;//分数
//表信息:
L.length;// 线性表长度
L.listsize;// 线性表规模
②静态
SqList L;
//第i个人的信息存储在:
L.data[i-1].name;//姓名
L.data[i-1].age;//年龄
L.data[i-1].sex;//性别
L.data[i-1].score;//分数
//表信息:
L.length;// 线性表长度
L.listsize;// 线性表规模
3、基本操作的实现
(1)构造一个空的线性表
①动态
bool InitList(SqList& L)
{
L.elem = (ElemType*)malloc(LIST_INIT_SIZE * sizeof(ElemType));//给线性表申请一个动态空间
if (!L.elem)//判断申请是否成功
{
cout << "未申请成功!" << endl;
return false;
}
//如果申请成功⬇
L.length = 0;
L.listsize = LIST_INIT_SIZE;
return true;
}
②静态
bool InitList(SqList& L)
{
L.length = 0;
L.listsize = 30;
return true;
}
(2)判断线性表L是否为空
①动态
bool ListEmpty(SqList L)
{
if (L.length == 0)
return true;
else
return false;
}
②静态
bool ListEmpty(SqList L)
{
if (L.length == 0)
return true;
else
return false;
}
(3)判断线性表L是否满
bool ListFull(SqList L)
{
if (L.length >= L,listsize)
{
return true;
}
return false;
}
(4)求L的长度
int ListLength(SqList L)
{
return L.length;
}
(5)求前驱的值
①动态
数组实现
bool PriorElem(SqList L, ElemType cur_e, ElemType& e)//第一个元素无前驱
{
//数组实现
int i;
for (i = 0; i < L.length; ++i)
{
if (i = 0 && L.elem[i].name == cur_e.name)
{
cout << "当前元素为第一个元素,无前驱" << endl;
return false;
}
if (i != 0 && L.elem[i].name == cur_e.name)
{
e = L.elem[i - 1];
return true;
}
}
cout << "顺序表中无当前值!" << endl;
return false;
}
指针实现
bool PriorElem(SqList L, ElemType cur_e, ElemType& e)//第一个元素无前驱
{
//指针实现
ElemType* p;
p = L.elem;
int i;
for (i = 0; i < L.length; ++i) //顺序表长度已知,故用for循环
{
if (i == 0 && p->name == cur_e.name)
{
cout << "当前元素为第一个元素,无前驱" << endl;
return false;
}
if (i != 0 && p->name == cur_e.name) //找到了当前元素且不是第一个元素,
{
e = *(--p); //将其前驱赋给引用参数
return true;
}
++p;
}
cout << "顺序表中无当前值!" << endl;
return false;
}
②静态
数组实现
bool PriorElem(SqList L, ElemType cur_e, ElemType& e)//第一个元素无前驱
{
//数组实现
int i;
for (i = 0; i < L.length; ++i)
{
if (i = 0 && L.data[i].name == cur_e.name)
{
cout << "当前元素为第一个元素,无前驱" << endl;
return false;
}
if (i != 0 && L.data[i].name == cur_e.name)
{
e = L.data[i - 1];
return true;
}
}
cout << "顺序表中无当前值!" << endl;
return false;
}
指针实现
bool PriorElem(SqList L, ElemType cur_e, ElemType& e)//第一个元素无前驱
{
//指针实现
ElemType* p;
p = L.data;
int i;
for (i = 0; i < L.length; ++i) //顺序表长度已知,故用for循环
{
if (i == 0 && p->name == cur_e.name)
{
cout << "当前元素为第一个元素,无前驱" << endl;
return false;
}
if (i != 0 && p->name == cur_e.name) //找到了当前元素且不是第一个元素,
{
e = *(--p); //将其前驱赋给引用参数
return true;
}
++p;
}
cout << "顺序表中无当前值!" << endl;
return false;
}
(6)求后继的值
①动态
数组实现
bool NextElem(SqList L, ElemType cur_e, ElemType& e)//最后一个元素无后继
{
//数组实现
int i;
for (i = 0; i < L.length; ++i)
{
if (i = L.length - 1 && L.elem[i].name == cur_e.name)
{
cout << "当前元素为最后一个元素,无后继" << endl;
return false;
}
if (i != L.length - 1 && L.elem[i].name == cur_e.name)
{
e = L.elem[i + 1];
return true;
}
}
cout << "顺序表中无当前值!" << endl;
return false;
}
指针实现
bool NextElem(SqList L, ElemType cur_e, ElemType& e)//最后一个元素无后继
{
//指针实现
ElemType* p;
p = L.elem;
int i;
for (i = 0; i < L.length; ++i) //顺序表长度已知,故用for
{
if (i == L.length - 1 && p->name == cur_e.name)
{
cout << "当前元素为最后一个元素,无后继" << endl;
return false;
}
if (i != L.length - 1 && p->name == cur_e.name)
{
e = *(++p); //将后继赋给引用参数带回
return true;
}
++p;
}
cout << "顺序表中无当前值!" << endl;
return false;
}
②静态
数组实现
bool NextElem(SqList L, ElemType cur_e, ElemType& e)//最后一个元素无后继
{
//数组实现
int i;
for (i = 0; i < L.length; ++i)
{
if (i = L.length - 1 && L.data[i].name == cur_e.name)
{
cout << "当前元素为最后一个元素,无后继" << endl;
return false;
}
if (i != L.length - 1 && L.elem[i].name == cur_e.name)
{
e = L.data[i + 1];
return true;
}
}
cout << "顺序表中无当前值!" << endl;
return false;
}
指针实现
bool NextElem(SqList L, ElemType cur_e, ElemType& e)//最后一个元素无后继
{
//指针实现
ElemType* p;
p = L.data;
int i;
for (i = 0; i < L.length; ++i) //顺序表长度已知,故用for
{
if (i == L.length - 1 && p->name == cur_e.name)
{
cout << "当前元素为最后一个元素,无后继" << endl;
return false;
}
if (i != L.length - 1 && p->name == cur_e.name)
{
e = *(++p); //将后继赋给引用参数带回
return true;
}
++p;
}
cout << "顺序表中无当前值!" << endl;
return false;
}
(7)取i位置的值
①动态
bool GetElem(SqList L, int i, ElemType& e)
{
if (i<1 || i>L.length)
{
cout << "i不合法!" << endl;
return false;
}
e = L.elem[i - 1];
return true;
}
②静态
bool GetElem(SqList L, int i, ElemType& e)
{
if (i<1 || i>L.length)
{
cout << "i不合法!" << endl;
return false;
}
e = L.data[i - 1];
return true;
}
(8)在线性表L中查找e(无则返回0)
①动态
int LocateElem(SqList L, ElemType e, bool compare(ElemType e,ElemType m))//函数名做参数
{
int i = 1;
while (i <= L.length && !compare(e, L.elem[i - 1]))
++i;
if (i <= L.length)
return i;
else
return 0;
}
bool Compare(ElemType e, ElemType m)
{
if (e.name == m.name && e.score == m.score)
return true;
else
return false;
}
②静态
int LocateElem(SqList L, ElemType e, bool compare(ElemType e, ElemType m))//函数名做参数
{
int i = 1;
while (i <= L.length && !compare(e, L.data[i - 1]))
++i;
if (i <= L.length)
return i;
else
return 0;
}
bool Compare(ElemType e, ElemType m)
{
if (e.name == m.name && e.score == m.score)
return true;
else
return false;
}
(9)遍历线性表
①动态
void ListTraverse(SqList L, void visit(ElemType e))//函数名做参数
{
int i;
for (i = 0; i < L.length; i++)
visit(L.elem[i]);
}
//--输出一个数据元素--
void Print(ElemType e)
{
cout << e.name << '\t' << e.score << endl;
}
②静态
void ListTraverse(SqList L, void visit(ElemType e))//函数名做参数
{
int i;
for (i = 0; i < L.length; i++)
visit(L.data[i]);
}
void Print(ElemType e)
{
cout << e.name << '\t' << e.score << endl;
}
(10)清空线性表
①动态
bool ClearList(SqList& L)
{
L.length = 0;
return true;
}
②静态
bool ClearList(SqList& L)
{
L.length = 0;
return true;
}
(11)销毁线性表
①动态
bool DestroyList(SqList& L)
{
free(L.elem);
L.length=0;
L.listsize=0;
return true;
}
②静态
bool DestroyList(SqList& L)
{
free(L.data);
L.length=0;
L.listsize=0;
return true;
}
区分清空&销毁线性表
清空:删除线性表中的数据,但是不删除线性表
销毁:既删除线性表中的数据,有释放掉线性表的存储空间
(12)在i位置前插入e
①动态
A.数组操作
bool ListInsert(SqList& L, int i, ElemType e)
{
if (i<1 || i>L.length + 1)//判断插入位置是否合法
{
cout << "插入位置不合法!" << endl;
return false;
}
if (L.length > L.listsize)//判断是否需要增加空间
{
cout << "当前存储空间已满,增加分配!" << endl;
ElemType* newbase;
newbase = (ElemType*)realloc(L.elem, L.listsize + LISTINCREMENT * sizeof(ElemType));
if (!newbase)//判断是否申请成功
{
cout << "申请失败!" << endl;
return false;
}
L.elem = newbase;
L.listsize += LISTINCREMENT;
}
//数组实现
int j;
for (j = L.length - 1; i > i - 1; --j)//插入位置及之后的元素后移
L.elem[j + 1] = L.elem[j];
L.elem[i - 1] = e;
++L.length;//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
return true;
}
B.指针操作
bool ListInsert(SqList& L, int i, ElemType e)
{
if (i<1 || i>L.length + 1)//判断插入位置是否合法
{
cout << "插入位置不合法!" << endl;
return false;
}
if (L.length > L.listsize)//判断是否需要增加空间
{
cout << "当前存储空间已满,增加分配!" << endl;
ElemType* newbase;
newbase = (ElemType*)realloc(L.elem, L.listsize + LISTINCREMENT * sizeof(ElemType));
if (!newbase)//判断是否申请成功
{
cout << "申请失败!" << endl;
return false;
}
L.elem = newbase;
L.listsize += LISTINCREMENT;
}
//指针实现
ElemType *p, *q;
q = &(L.elem[i - 1]);
for (p = &(L.elem[L.length - 1]); p >= q; --p)
*(p + 1) = *p;
*q = e;
++L.length;//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
return true;
}
②静态
A.数组操作
bool ListInsert(SqList& L, int i, ElemType e)
{
if (i<1 || i>L.length + 1)//判断插入位置是否合法
{
cout << "插入位置不合法!" << endl;
return false;
}
//数组实现
int j;
for (j = L.length - 1; i > i - 1; --j)//插入位置及之后的元素后移
L.data[j + 1] = L.data[j];
L.data[i - 1] = e;
++L.length;//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
return true;
}
B.指针操作
bool ListInsert(SqList& L, int i, ElemType e)
{
if (i<1 || i>L.length + 1)//判断插入位置是否合法
{
cout << "插入位置不合法!" << endl;
return false;
}
//指针实现
ElemType *p, *q;
q = &(L.data[i - 1]);
for (p = &(L.data[L.length - 1]); p >= q; --p)
*(p + 1) = *p;
*q = e;
++L.length;//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
return true;
}
(13)删除i位置的元素——往前移就行
①动态
A.数组操作
bool ListDelete(SqList& L, int i, ElemType& e)
{
if (i<1 || i>L.length)//判断删除位置是否合理
{
cout << "删除位置不合法!" << endl;
return false;
}
//数组实现
e = L.elem[i - 1];//记录删除的元素
int j;
for (j = i - 1; j < L.length - 1; ++i)
L.elem[j] = L.elem[j + 1];
--L.length;//!!!!!
return true;
}
B.指针操作
bool ListDelete(SqList& L, int i, ElemType& e)
{
if (i<1 || i>L.length)//判断删除位置是否合理
{
cout << "删除位置不合法!" << endl;
return false;
}
//指针实现
ElemType *p, *q;
p = &(L.elem[i - 1]);
e = *p;
q = L.elem + L.length - 1;
for (++p; p <= q; ++p)
*(p - 1) = *p;
--L.length;//!!!!!
return true;
}
②静态
A.数组操作
bool ListDelete(SqList& L, int i, ElemType& e)
{
if (i<1 || i>L.length)//判断删除位置是否合理
{
cout << "删除位置不合法!" << endl;
return false;
}
//数组实现
e = L.data[i - 1];//记录删除的元素
int j;
for (j = i - 1; j < L.length - 1; ++i)
L.data[j] = L.data[j + 1];
--L.length;//!!!!!!!!!1
return true;
}
B.指针操作
bool ListDelete(SqList& L, int i, ElemType& e)
{
if (i<1 || i>L.length)//判断删除位置是否合理
{
cout << "删除位置不合法!" << endl;
return false;
}
//指针实现
ElemType *p, *q;
p = &(L.data[i - 1]);
e = *p;
q = L.data + L.length - 1;
for (++p; p <= q; ++p)
*(p - 1) = *p;
--L.length;//!!!!!!!!!!
return true;
}
二、链表
(一)单链表
不带头节点
带头节点
1、头文件
#include <iostream>
#include <stdlib.h>
using namespace std;
2、链式存储的实现
(1)元素数据类型自定义
//元素数据类型
typedef struct
{
char name[8];
float score;
} ElemType;
(2)节点数据类型LNode与链表数据类型LinkList
//单链表
typedef struct LNode
{
ElemType data;
LNode* next;
}LNode,*LinkList;
LinkList和LNode *是一样的
(3)节点数据类型的操作、实现
①第一种方式
LNode L;
两个域的表示方法分别为:
L.data;//数据域
L.next;//指针域
②第二种方式
LNode *L
//等价于
LinkList L
L = (LNode*) malloc(sizeof (LNode));
两个域的表示方法分别为:
L->data;//数据域
L->next;//指针域
3、单链表基本操作
(1)链表初始化
①带头节点
void InitList(LinkList& L)
{
L = (LNode*)malloc(sizeof(LNode));
L->next = NULL;
}
bool InitList(LinkList& L)
{
L = (LNode*)malloc(sizeof(LNode));
if (!L)
{
cout << "申请头节点内存失败!" << endl;
return false;
}
L->next = NULL;
return true;
}
②不带头节点
void InitList(LinkList& L)
{
L = (LNode*)malloc(sizeof(LNode));
L=NULL;
}
(2)判断链表是否为空
①带头节点
bool ListEmpty(LinkList L)
{
//带头结点
if (L->next == NULL)
return true;
else
return false;
}
②不带头结点
bool ListEmpty(LinkList L)
{
if (L == NULL)
return true;
else
return false;
}
(3)清空链表
除了头节点,从第一个节点开始均free
void ClearList(LinkList& L)
{
LNode* p, * q;
p = L->next;
while (p)
{
q = p;
p = p->next;
free(q);
}
}
不带头结点的与销毁链表相同
(4)销毁链表
从头节点开始每一个节点都free
void DestroyList(LinkList& L)
{
LNode* p, * q;
p = L;
while (p)
{
q = p;
p = p->next;
free(q);
}
}
(5)求L的长度
有1个节点计数一个
①带头节点
int ListLenngth(LinkList L)
{
LNode* p;
p = L->next;
int i = 0;
while (p)
{
++i;
p->next;
}
return i;
}
②不带头节点
int ListLenngth(LinkList L)
{
LNode* p;
p = L;
int i = 0;
while (p)
{
++i;
p->next;
}
return i;
}
(6)求前驱的值——不好整
①带头节点
bool PriorElem(LinkList L, ElemType cur_e, ElemType& e)
{
if (L->next->data.name == cur_e.name)//检测第一个结点
{
cout << "当前元素为第一个元素,无前驱" << endl;
return false;
}
LNode* p;
p = L->next;
while (p->next)
{
if (p->next->data.name == cur_e.name)
{
e = p->data;
}
p = p->next;
}
cout << "链表中无当前值!" << endl;
return false;
}
②不带头节点
bool PriorElem(LinkList L, ElemType cur_e, ElemType& e)
{
if (L->data.name == cur_e.name)//检测第一个结点
{
cout << "当前元素为第一个元素,无前驱" << endl;
return false;
}
LNode* p;
p = L;
while (p->next)
{
if (p->next->data.name == cur_e.name)
{
e = p->data;
}
p = p->next;
}
cout << "链表中无当前值!" << endl;
return false;
}
(7)求后继的值
①带头结点
bool NextElem(LinkList L, ElemType cur_e, ElemType& e)
{
LNode* p;
p = L->next;
while (p)
{
if (p->next == NULL && p->data.name == cur_e.name)
{
cout << "当前元素为最后一个元素,无后继" << endl;
return false;
}
if (p->next != NULL && p->data.name == cur_e.name)
{
e = p->next->data;
return true;
}
p = p->next;
}
cout << "链表中无当前值!" << endl;
return false;
}
②不带头节点
bool NextElem(LinkList L, ElemType cur_e, ElemType& e)
{
LNode* p;
p = L;
while (p)
{
if (p->next == NULL && p->data.name == cur_e.name)
{
cout << "当前元素为最后一个元素,无后继" << endl;
return false;
}
if (p->next != NULL && p->data.name == cur_e.name)
{
e = p->next->data;
return true;
}
p = p->next;
}
cout << "链表中无当前值!" << endl;
return false;
}
(8)在线性表中查找e
①带头节点
int LocateElem(LinkList L, ElemType e, bool compare(ElemType e, ElemType m))
{
LNode* p;
int i;
p = L->next;
i = 1;
while (p && !compare(e, p->data))
{
p->next;
++i;
}
if (p)
{
return i;
}
else
{
cout << "没找到!" << endl;
return 0;
}
}
bool Compare(ElemType e, ElemType m)
{
if (e.name == m.name && e.score == m.score)
return true;
else
return false;
}
②不带头节点
int LocateElem(LinkList L, ElemType e, bool compare(ElemType e, ElemType m))
{
LNode* p;
int i;
p = L;
i = 1;
while (p && !compare(e, p->data))
{
p->next;
++i;
}
if (p)
{
return i;
}
else
{
cout << "没找到!" << endl;
return 0;
}
}
bool Compare(ElemType e, ElemType m)
{
if (e.name == m.name && e.score == m.score)
return true;
else
return false;
}
(9)遍历线性表
②带头节点
void ListTraverse(LinkList L, void visit(ElemType e))
{
LNode* p;
p = L->next;
while (p)
{
visit(p->data);
p=p->next;//++p❌
}
}
void Print(ElemType e)
{
cout << e.name << "\t" << e.score << endl;
}
①不带头节点
void ListTraverse(LinkList L, void visit(ElemType e))
{
LNode* p;
p = L;
while (p)
{
visit(p->data);
p=p->next;//++p❌
}
}
void Print(ElemType e)
{
cout << e.name << "\t" << e.score << endl;
}
(10)取i位置的值!!!
遍历、计数
①带头节点
bool GetElem(LinkList L, int i, ElemType& e)
{
LNode* p;//LinkList p;
int j;
//从第一个结点p开始,j=1开始记数
p = L->next;
j = 1;//p指向第一个节点,j为计数器
//若无头结点:p=L;
while (p && j < i)//当p->next不为空时,转向下一个继续记数;直到j=i或p->next为空(可能味道i就没了)
{
p = p->next;
++j;
}
if (!p || j > i - 1)//判断第i个元素是否存在//i大于表长或者小于1
{
cout << "i不在合理范围内,程序结束" << endl;
return false;
}
e = p->data;//取得第i个元素
return true;
}
②不带头结点
bool GetElem(LinkList L, int i, ElemType& e)
{
LNode* p;//LinkList p;
int j;
//从第一个结点p开始,j=1开始记数
p = L;
j = 1;//p指向第一个节点,j为计数器
//若无头结点:p=L;
while (p && j < i)//当p->next不为空时,转向下一个继续记数;直到j=i或p->next为空(可能味道i就没了)
{
p = p->next;
++j;
}
if (!p || j > i - 1)//判断第i个元素是否存在//i大于表长或者小于1
{
cout << "i不在合理范围内,程序结束" << endl;
return false;
}
e = p->data;//取得第i个元素
return true;
}
(11)在i位置插入e!!!
要找到i-1个(前一个)元素地址,再插入
①带头节点
bool ListInsert(LinkList& L, int i, ElemType e)
{
// L 为带头结点的单链表的头指针,本算法在链表中第i个结点之前插入新的元素e
LNode* p;
int j;
p = L->next;
j = 1;
while (p && j < i - 1)//查找第i-1个结点p
{
p = p->next;
++j;
}
if (!p || j > i)//i大于表长或者小于1
{
cout << "i不在合理范围内,程序结束" << endl;
return false;
}
LNode* s;
s = (LNode*)malloc(sizeof(LNode));
//插入
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
②不带头节点
bool ListInsert(LinkList& L, int i, ElemType e)
{
LNode* p;
int j;
p = L;
j = 1;
while (p && j < i - 1)//查找第i-1个结点p
{
p = p->next;
++j;
}
if (!p || j > i)//i大于表长或者小于1
{
cout << "i不在合理范围内,程序结束" << endl;
return false;
}
LNode* s;
s = (LNode*)malloc(sizeof(LNode));
//插入
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
(12)删除i位置的元素!!!
要找到i-1个(前一个)元素地址,再删除
①带头节点
bool ListDelete(LinkList& L, int i, ElemType& e)
{
LNode* p;
int j;
p = L->next;
j = 1;
while (p && j < i - 1)//查找第i-1个结点p
{
p = p->next;
++j;
}
if (!p || j > i)//i大于表长或者小于1
{
cout << "i不在合理范围内,程序结束" << endl;
return false;
}
LNode* q;
q = p->next;
p->next = q->next;
e = q->data;
free(q);
return true;
}
②不带头节点
bool ListDelete(LinkList& L, int i, ElemType& e)
{
LNode* p;
int j;
p = L;
j = 1;
while (p && j < i - 1)//查找第i-1个结点p
{
p = p->next;
++j;
}
if (!p || j > i)//i大于表长或者小于1
{
cout << "i不在合理范围内,程序结束" << endl;
return false;
}
LNode* q;
q = p->next;
p->next = q->next;
e = q->data;
free(q);
return true;
}
(13)建立一个长度为n的单链表L
void Input(ElemType& e)
{
cin >> e.name;
cin >> e.score;
}
①头插法
void CreatList_T(LinkList& L, int n)
{
ElemType e;
int i;
for (i = 0; i <= n; ++i)
{
Input(e);
ListInsert(L, 1, e);
}
}
②尾插法
void CreateList_W(LinkList& L, int n)
{
ElemType e;
int i;
for (i = 1; i <= n; ++i)
{
cout << "输入结点" << i << "的:" << endl;
Input(e);
ListInsert(L, i, e);
}
}
(14)头插法
LNode* r, * p;
r = L;
p = (LNode*)malloc(sizeof(LNode));
p->data = e;
r->next = p;
r = p;
r->next = NULL;
(15)尾插法
LNode* r, * p;
r = L;
p = (LNode*)malloc(sizeof(LNode));
p->data = e;
r->next = p;
r = p;
r->next = NULL;
(二)循环链表
(1)La、Lb都是带头结点的单链表,如何实现将Lb接在La之后?时间复杂度是多少?
R=La;
While(R->next)
R=R->next;
R->next=Lb->next;
free(Lb)
(2)La、Lb都是带头结点头指针的单循环链表,如何实现将Lb接在La之后还形成一个循环链表?时间复杂度是多少?
R1=La;
While(R1->next!=La)
R1=R->next;
R2=Lb;
While(R2->next!=Lb)
R2=R->next;
R1->next=Lb->next;
R2->next=La;
free(Lb)
(3)La、Lb都是带头结点尾指针的单循环链表,如何实现将Lb接在La之后还形成一个循环链表??
L1=La->next;
L2=Lb->next;
La->next=L2->next;
Lb->next=L1;
free(L2)
(三)双向链表
1、类型定义
typedef struct DuLNode
{
ElemType data;
struct DuLNode *prior;//前驱
struct DuLNode *next;//后继
} DuLNode,*DuLinkList;
2、基本操作
(1)在a节点前插入一个节点,其值为e
bool ListInsert_Dul(DuLinkList& L, int i, ElemType e)
{
if (!(p = GetElemP_DuL(L, i)))
{
cout << "i不合法!" << endl;
return false;
}
if (!(s = (DuLinkList)malloc(sizeof(DuLNode))))
{
cout << "申请空间失败!" << endl;
return false;
}
s->data = e;
s->prior = p->prior;
s->next = p;
p->prior->next = s;
p->prior = s;
return true;
}
(2)删除a节点
bool ListDelete_Dul(DuLinkList& L, int i, ElemType& e)
{
if (!(p = GetElemP_DuL(L, i)))
{
cout << "i不合法!" << endl;
return false;
}
e = p->data;
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
return OK;
}
第3章 栈和队列
一、栈
(一)顺序栈
1、头文件
#include <iostream>
#include <stdlib.h>
using namespace std;
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
2、类型定义
(1)元素数据类型
typedef char SElemType;
(2)顺序栈数据类型
typedef struct
{
SElemType* base;
SElemType* top;
int stacksize;
}SqStack;
3、基本操作
(1)初始化栈
bool InitStack(SqStack& S)
{
S.base = (SElemType*)malloc(STACK_INIT_SIZE * sizeof(SElemType));
if (!S.base)
{
cout << "申请空间失败!" << endl;
return false;
}
S.top = S.base;//!!!
S.stacksize = STACK_INIT_SIZE;
return true;
}
(2)销毁栈
bool DestroyStack(SqStack& S)
{
free(S.base);//主要操作!
S.base = NULL;//避免野指针生成
S.top = NULL;//避免野指针生成
S.stacksize = 0;
return true;
}
(3)清空栈
bool ClearStack(SqStack& S)
{
S.top = S.base;
return true;
}
(4)判栈空
bool StackEmpty(SqStack S)
{
if (S.top == S.base)
{
cout << "栈空!" << endl;
return true;
}
return false;
}
(5)判栈满
bool StackFull(SqStack S)
{
if (S.top - S.base >= S.stacksize)//判栈满
{
cout << "栈满!" << endl;
return true;
}
return false;
}
(6)求栈长度
int StackLength(SqStack S)
{
return S.top - S.base;
}
(7)取栈顶元素
bool GetTop(SqStack S, SElemType& e)
{
if (S.top == S.base)
{
cout << "栈空!" << endl;
return false;
}
e = *(S.top - 1);
return true;
}
(8)入栈
bool Push(SqStack& S, SElemType e)
{
if (S.top - S.base >= S.stacksize)//判栈满
{
cout << "栈满!申请新空间!" << endl;
S.base = (SElemType*)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(SElemType));
if (!S.base)
{
cout << "申请新空间失败" << endl;
return false;
}
S.top = S.base + S.stacksize;//更改栈顶位置
S.stacksize += STACKINCREMENT;
}
*S.top = e;
++S.top;
//或者写成*S.top++ = e;
return true;
}
(9)出栈
bool Pop(SqStack& S, SElemType& e)
{
if (S.top == S.base)
{
cout << "栈空!" << endl;
return false;
}
e = *(S.top - 1);
--S.top;
return true;
}
(10)遍历栈
void StackTravers(SqStack S, void visit(SElemType e))
{
while (S.top > S.base)
visit(*S.base++);
}
void Visit(SElemType e)
{
cout << e << endl;
}
(二)链栈
1、头文件
#include <iostream>
#include <stdlib.h>
using namespace std;
2、类型定义
(1)元素数据类型
typedef char SElemType;
(2)链栈数据类型
typedef struct SNode
{
SElemType data;
struct SNode* next;
}SNode,*LinkStack;
3、基本操作
(1)初始化栈
建立一个只有头节点空的链表
bool InitStack(LinkStack& S)
{
S = (SNode*)malloc(sizeof(SNode));
if (!S)
{
cout << "内存申请失败!" << endl;
return false;
}
S->next = NULL;
return true;
}
(2)销毁栈
从头结点开始一个一个释放
bool DestroyStack(LinkStack& S)
{
SNode* p;
while (S->next != NULL)
{
p = S->next;
S->next = p->next;
free(p);
}
free(S);//!!!
return true;
}
(3)清空栈
除了头结点,从第一个节点开始一个一个释放
bool ClearStack(LinkStack& S)
{
SNode* p;
while (S->next != NULL)
{
p = S->next;
S->next = p->next;
free(p);
}
return true;
}
(4)判栈空
bool StackEmpty(LinkStack S)
{
if (S->next == NULL)
{
cout << "栈空!" << endl;
return true;
}
return false;
}
(5)求栈长度
int StackLength(LinkStack S)
{
int i = 0;
SNode* p;
p = S;
while (p->next != NULL)
{
++i;
p = p->next;
}
return i;
}
(6)取栈顶元素
bool GetTop(LinkStack S, SElemType& e)
{
if (StackEmpty(S))//判空
{
cout << "栈空!" << endl;
return false;
}
e = S->next->data;
return true;
}
(7)入栈
bool Push(LinkStack& S, SElemType e)
{
SNode* p;
p = (SNode*)malloc(sizeof(SNode));
if (!p)
{
cout << "内存申请失败!" << endl;
return false;
}
p->data = e;
p->next = S->next;
S->next = p;
return true;
}
(8)出栈
bool Pop(LinkStack& S, SElemType& e)
{
if (S->next == NULL)//判空
{
cout << "空栈" << endl;
return false;
}
SNode* p;
p = S->next;
e = p->data;
S->next = p->next;
free(p);
return true;
}
(9)遍历栈
void StackTravers(LinkStack S, void visit(SElemType e))
{
SNode* p;
p = S;
while (p->next != NULL)
{
visit(p->next->data);
p = p->next;
}
}
void Visit(SElemType e)
{
cout << e << endl;
}
二、队列
(一)链队列
1、头文件
#include <iostream>
#include <stdlib.h>
using namespace std;
2、类型定义
(1)元素数据类型
typedef char QElemType;
(2)链队列实现
①链队列结点数据类型
typedef struct QNode
{
QElemType data;
struct QNode* next;
}QNode,*QueuePtr;
②链队列数据类型
typedef struct
{
QueuePtr front; // 队头指针
QueuePtr rear; // 队尾指针
}LinkQueue;
3、基本操作
(1)初始化队列——带头节点
bool InitQueue(LinkQueue& Q)
{
Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));
if (!Q.front)
{
cout << "申请空间失败!" << endl;
return false;
}
Q.front->next = NULL;
return true;
}
(2)清空队列
头指针和尾指针都指向头节点,其他节点均free
bool ClearQueue(LinkQueue& Q)
{
QueuePtr p;
p = Q.front->next;
while (p)
{
free(p);
p = Q.front->next;
}
Q.rear = Q.front;
return true;
}
(3)销毁队列
先free头节点,再释放其他节点
bool DestroyQueue(LinkQueue& Q)
{
QueuePtr p;
p = Q.front;
while (p)
{
Q.front = Q.front->next;
free(p);
p = Q.front;
}
return true;
}
(4)判队列是否空
bool QueueEmpty(LinkQueue Q)
{
if (Q.front == Q.rear)//KEY
{
cout << "队列空" << endl;
return true;
}
cout << "队列不为空!" << endl;
return false;
}
(5)求队列长度
遍历队列
int QueueLength(LinkQueue Q)
{
//return Q.rear - Q.front;❌这里不是顺序存储
QueuePtr p;
int i = 0;
p = Q.front;
//等价于↓
/*
int i = 1;
p = Q.front->next;
*/
while (p != Q.rear)
{
++i;
p = p->next;
}
return i;
}
(6)取队头元素
bool GetHead(LinkQueue Q, QElemType& e)
{
QueuePtr p;
if (Q.front == Q.rear)
{
cout << "队列空!" << endl;
return false;
}
e = Q.front->next->data;
return true;
}
(7)入队列
bool EnQueue(LinkQueue& Q, QElemType e)
{
QueuePtr p;
p = (QueuePtr)malloc(sizeof(QNode));
if (!p)
{
cout << "申请新节点失败!" << endl;
return false;
}
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return true;
}
(8)出队列
bool DeQueue(LinkQueue& Q, QElemType& e)
{
if (Q.front == Q.rear)
{
cout << "队列空" << endl;
return false;
}
QueuePtr p;
p = Q.front->next;
e = p->data;
Q.front->next = p->next;
if (Q.rear == p)//如果p所在的节点是尾节点,要更改尾节点!!!
{
Q.rear == Q.front;
}
free(p);//不要忘写!!!
return true;
}
(9)遍历队列
void QueueTravers(LinkQueue Q, void visit(QElemType e))
{
QueuePtr p;
p = Q.front->next;
while (p)
{
visit(p->data);
p = p->next;
}
}
void Visit(QElemType e)
{
cout << e << endl;
}
(二)循环队列
1、头文件
#include <iostream>
#include <stdlib.h>
using namespace std;
#define MAXQSIZE 100//最大队列长度//不要加“;”
2、类型定义
(1)元素数据类型
typedef char QElemType;
(2)顺序队列数据类型
typedef struct
{
QElemType* base;
int front;
int rear;
}SqQueue;
3、基本操作
(1)初始化队列
bool InitQueue(SqQueue& Q)
{
Q.base = (QElemType*)malloc(MAXQSIZE * sizeof(QElemType));
if (!Q.base)
{
cout << "申请空间失败!" << endl;
return false;
}
Q.front = Q.rear = 0;
return true;
}
(2)清空队列
bool ClearQueue(SqQueue& Q)
{
Q.front = Q.rear = 0;
return 0;
}
(3)销毁队列
bool DestroyQueue(SqQueue& Q)
{
free(Q.base);
Q.front = Q.rear = 0;
return true;
}
(4)判队列是否空
bool QueueEmpty(SqQueue Q)
{
if (Q.front == Q.rear)
{
cout << "队列空!" << endl;
return true;
}
return false;
}
(5)判队列是否满
bool QueueFull(SqQueue Q)
{
if ((Q.rear + 1) % MAXQSIZE == Q.front)
{
cout << "队列满!" << endl;
return true;
}
return false;
}
(6)求队列长度
bool QueueLength(SqQueue Q)
{
return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;//important!
}
(7)取队头元素
bool GetHead(SqQueue Q, QElemType& e)
{
if (Q.front == Q.rear)//判空
{
cout << "队列空" << endl;
return false;
}
e = Q.base[Q.front];
return true;
}
(8)入队列
bool EnQueue(SqQueue& Q, QElemType e)
{
if ((Q.rear + 1) % MAXQSIZE == Q.front)//判满
{
cout << "队列满" << endl;
return false;
}
Q.base[Q.rear] = e;
Q.rear = (Q.rear + 1) % MAXQSIZE;//important!
return true;
}
(9)出队列
bool DeQueue(SqQueue& Q, QElemType& e)
{
if (Q.front == Q.rear)//判空
{
cout << "队列空" << endl;
return false;
}
e = Q.base[Q.front];
Q.front = (Q.front + 1) % MAXQSIZE;//important!//不用free
}
(10)遍历队列
void QueueTravers(SqQueue Q, void visit(QElemType e))
{
int i;
i = Q.front;
while (i != Q.rear)
{
visit(Q.base[i]);
i = (i + 1) % MAXQSIZE;
}
}
void Visit(QElemType e)
{
cout << e << endl;
}
(三)双端队列
1、定义
双端队列是一种特殊的线性表,特殊在限定插入和删除操作只能在表的两端进行。
2、双端队列示意图
附录 · 栈和队列基本库用法
1、栈
//栈的头文件
#include<stack>
//定义栈如下:
stack<ElemType>s;
//栈的基本操作:
s.empty() //如果栈为空返回true,否则返回false
s.size() //返回栈中元素的个数
s.push() //在栈顶压入新元素
s.pop() //删除栈顶元素但不返回其值
s.top() //返回栈顶的元素,但不删除该元素
2、队列
//队列的头文件
#include<queue>
//定义队列如下:
queue<ElemType>Q;
//队列的基本操作
q.empty() //如果队列为空返回true,否则返回false
q.size() //返回队列中元素的个数
q.push() //在队尾压入新元素
q.pop() //删除队列首元素但不返回其值
q.front() //返回队首元素的值,但不删除该元素
q.back() //返回队列尾元素的值,但不删除该元素
第4章 串
一、串的表示和实现
1、定长顺序存储表示
(1)字符串数据结构
#define MAXSTRLEN 255// 用户可在255以内定义最大串长
typedef unsigned char Sstring[MAXSTRLEN + 1];
// 0号单元存放串的长度
(2)字符串转化——0号单元存放串的长度
void StrAssign(char* temp, Sstring& S)
{
int len;
len = strlen(temp);
int i;
S[0] = len;
for (i = 0; i < len; ++i)
{
S[i + 1] = temp[i];
}
}
2、堆分配存储表示
typedef struct
{
char* ch;
// 若是非空串,则按串长分配存储区,
// 否则ch为NULL
int length;// 串长度
}HString;
3、串的块链存储表示
//存一个数据结构 → 结点
//存多个数据结构 → 块
#define CHUNKSIZE 80
//结点结构
typedef struct Chunk
{
char ch[CHUNKSIZE];//存一个小的字符串→字符初速
struct Chunk* next;
}Chunk;
//串的链式结构
typedef struct
{
Chunk* head, * tail;//串的头指针和尾指针
int curlen;//串的当前长度
}LString;
//若最后一块字符串不够,要在后面加特殊字符补位如:“#”,But对串连接时要把后面“#”去掉再连
二、串的模式匹配算法
(一)模式匹配简单算法
1、串匹配(查找)的定义
INDEX (S, T, pos)
2、初始条件
串S和T存在,T是非空串,
1
≤
p
o
s
≤
S
t
r
L
e
n
g
t
h
(
S
)
1≤pos≤StrLength(S)
1≤pos≤StrLength(S)
3、操作结果
若主串S中存在和串T值相同的子串返回它在主串S中第pos个字符之后第一次出现的位置;
否则函数值为0。
4、程序实现
int Index(Sstring S, Sstring T, int pos)
{
int i, j;;
i = pos;
j = 1;
while (i <= S[0] && j <= T[0])
{
if (S[i] == T[j])
{
//继续比较后继字符
++i;
++j;
}
else
{
//指针后退重新开始匹配
i = i - j + 2;//主串回到开始匹配位置的后一个位置
j = 1;//模式串回到第一个
}
}
if (j > T[0])
return i - T[0];//i-j+1
else
return 0;
}
(二)模式匹配KMP算法
1、KMP算法游标定位的几种情况
(1)第一个字符不相等
(2)在子串j处不相等,在子串中j之前没有“前缀和后缀相等的情况“
(3)在子串j处不相等,在子串中j之前有“前后前缀和后缀相等的情况”
总结
j游标回到什么位置(一般称作next值),有三种情况:
(1) 第一个字符不相等。
此时:i++; 约定next值为0
(2) 在子串j处不相等,在子串中j之前没有“前缀和后缀相等的情况”。
此时:i不动,j回到1;next值为1
(3) 在子串j处不相等,在子串中j之前存在“前缀和后缀相等的情况”。
此时:回到“最长前后缀相等串”+1的位置;next值为最长前后相等子串的长度+1;
2、next[j]数组,求next函数值
(1)要求next[k+1] 其中k+1=17
(2)已知next[16]=8,则元素有以下关系:
(3)如果P8=P16,则明显next[17]=8+1=9
(4)如果不相等,又若next[8]=4,则有以下关系
(5)现在再判断,如果P16=P4则next[17]=4+1=5,否则,在继续递推
(6)若next[4]=2,则有以下关系
(7)若P16=P2,则next[17]=2+1=3;否则继续取next[2]=1、next[1]=0;遇到0时还没出结果,则递推结束,此时next[17]=1
3、代码实现
//next数组
void get_next(Sstring T, int next[])
{
int i, j;
i = 1;//字符位置
j = 0;//i的next值
next[1] = 0;
while (i < T[0])
{
if (j == 0 || T[i] == T[j])
{
++i;
++j;
next[i] = j;
}
else
j = next[j];
}
}
//KMP算法
int Index_KMP(Sstring S, Sstring T, int pos)
{
int i, j;
i = pos;
j = 1;
//引入next数组
int* next;
next = (int*)malloc(T[0] * sizeof(int));
get_next(T, next);
while (i < S[0] && j < T[0])
{
if (j == 0 || S[i] == T[j])
{
++i;
++j;
}
else
{
j = next[j];//i不变,j后退
}
}
if (j > T[0])//匹配成功
return i - T[0];
else //匹配失败
return 0;
}
第5章 数组和广义表
一、数组的顺序表示和实现
1、存储方式
数组在内存中主要采用两种存储方式:
(1)以行序为主的存储方式
(2)以列序为主的存储方式
不同的存储方式有不同元素地址计算方法。
2、数组元素的地址关系 (行序为主)
设每个元素所占空间为 L L L, A [ 0 ] [ 0 ] A[0][0] A[0][0]的起始地址记为 L O C [ 0 , 0 ] LOC[0,0] LOC[0,0]
(1)二维数组 A [ b 1 ] [ b 2 ] A[b1][b2] A[b1][b2]
元素
A
i
j
A_{ij}
Aij&
A
[
i
]
[
j
]
A[i][j]
A[i][j]的起始地址为:
L
O
C
[
i
,
j
]
=
L
O
C
[
0
,
0
]
+
(
b
2
×
i
+
j
)
×
L
LOC[i,j]=LOC[0,0]+(b_2\times i+j)\times L
LOC[i,j]=LOC[0,0]+(b2×i+j)×L
(2)三维数组 A [ b 1 ] [ b 2 ] [ b 3 ] A[b1][b2][b3] A[b1][b2][b3]
数据元素
A
i
,
j
,
k
A_{i,j,k}
Ai,j,k&
A
[
i
]
[
j
]
[
k
]
A[i][j][k]
A[i][j][k]的起始地址为:
L
O
C
[
i
,
j
,
k
]
=
L
O
C
[
0
,
0
,
0
]
+
(
b
2
×
b
3
×
i
+
b
3
×
j
+
k
)
×
L
LOC[i,j,k]=LOC[0,0,0]+(b_2\times b3\times i+b3\times j+k)\times L
LOC[i,j,k]=LOC[0,0,0]+(b2×b3×i+b3×j+k)×L
(3)n维数组 A [ b 1 ] [ b 2 ] . . . [ b n ] A[b1][b2]...[bn] A[b1][b2]...[bn]
数据元素
A
j
1
,
j
2
,
.
.
.
,
j
n
A_{j_1,j_2,...,j_n}
Aj1,j2,...,jn&
A
[
j
1
]
[
j
2
]
.
.
.
[
j
n
]
A[j_1][j_2]...[j_n]
A[j1][j2]...[jn]的存储位置为:
L
O
C
[
j
1
,
j
2
,
…
j
n
]
=
L
O
C
[
0
,
0
,
…
,
0
]
+
(
b
2
×
…
×
b
n
×
j
1
+
b
3
×
…
×
b
n
×
j
2
+
…
+
b
n
×
j
n
−
1
+
j
n
)
×
L
LOC[j1,j2,…jn] =LOC[0,0,…,0]+(b_2\times …\times b_n\times j_1+b_3 \times …\times b_n\times j_2+…+b_n\times j_{n-1}+j_n)\times L
LOC[j1,j2,…jn]=LOC[0,0,…,0]+(b2×…×bn×j1+b3×…×bn×j2+…+bn×jn−1+jn)×L
二、矩阵的压缩存储
1、下(上)三角矩阵
(1)定义
(2)存储方式
(3)压缩存储时地址对应关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MTjoqk2v-1638243978153)(https://bkimg.cdn.bcebos.com/formula/a25495da6d0d038829fed794c8378d2d.svg)]
设下三角矩阵为 a [ n ] [ n ] a[n][n] a[n][n],用一维数组 s a [ n × ( n + 1 ) / 2 ] sa[n\times (n+1)/2] sa[n×(n+1)/2]存储,则矩阵元素 a [ i ] [ j ] a[i][j] a[i][j]在数组 s a sa sa中的位置为:
当
i
≥
j
i\ge j
i≥j时(下三角矩阵),
a
i
j
a_{ij}
aij存储在
s
a
[
i
(
i
−
1
)
/
2
+
j
−
1
]
sa[i(i-1)/2+j-1]
sa[i(i−1)/2+j−1]
2、稀疏矩阵
(1)定义
(2)存储结构——三元组顺序表
#define MAXSIZE 12500 //元素最大个数
typedef int ElemType; //非零元元素数据类型
//三元组类型
typedef struct
{
int i, j; //该非零元的行下标和列下标
ElemType e; //该非零元的值
}Triple;
//稀疏矩阵类型
typedef struct
{
Triple data[MAXSIZE + 1]; //一维三元组数组//data[0]未用
int mu, nu, tu; //行数,列数,非零元个数
}TSMatrix;
(3)三元组顺序表转置的实现
①示例
②代码实现
存储结构——三元组顺序表
#define MAXSIZE 12500 //元素最大个数
typedef int ElemType; //非零元元素数据类型
//三元组类型
typedef struct
{
int i, j; //该非零元的行下标和列下标
ElemType e; //该非零元的值
}Triple;
//稀疏矩阵类型
typedef struct
{
Triple data[MAXSIZE + 1]; //一维三元组数组//data[0]未用
int mu, nu, tu; //行数,列数,非零元个数
}TSMatrix;
三元顺序表转置算法
bool TransposeSMatrix(TSMatrix M/*转置前矩阵顺序表*/, TSMatrix& T/*转置后矩阵顺序表*/)
{
int p, q, col;//转置前顺序表遍历,转置后顺序表遍历,转置前列数(转置后行数)
//初始化转置后矩阵大小
T.mu = M.nu;
T.nu = M.mu;
T.tu = M.tu;
if (T.tu)//转置前矩阵中有非零元素
{
q = 1;//存转置后顺序表第1个元素
for (col = 1; col <= M.nu; ++col)//转置前列数(转置后行数)从1到n
{
for (p = 1; p <= M.tu; ++p)//遍历转置前矩阵顺序表
{
if (M.data[p].j == col)//此时的列数和现在要找的转置前列数(转置后行数)相同
{
//赋值
T.data[q].i = M.data[p].j;
T.data[q].j = M.data[p].i;
T.data[q].e = M.data[p].e;
++q;//转置后顺序表指向下一个元素
}
}
}
return true;
}
return false;
}
③时间复杂性
三、广义表
1、定义
(1)广义表
(2)术语
(3)特点
2、存储结构
广义表通常采用头、尾指针的链表结构
(1) 广义表的头尾指针结点结构
typedef enum { ATOM/*原子*/, LIST/*子表*/ } ElemTag;
typedef int AtomType;
typedef struct GLNode
{
ElemTag tag; //标志位 //公共部分,用于区分原子结点和表结点
union //共用体 //原子结点和表结点的联合部分
{
AtomType atom; //原子结点的值域
struct //子表
{
struct GLNode* hp, * tp;//ptr.hp 和ptr.tp 分别指向表头和表尾
}ptr;//ptr 是表结点的指针域
};
}*GList;
(2)扩展头尾指针结点结构
typedef enum { ATOM/*原子*/, LIST/*子表*/ } ElemTag;
typedef int AtomType;
typedef struct GLNode
{
ElemTag tag; //标志位 //公共部分,用于区分原子结点和表结点
union //共用体 //原子结点和表结点的联合部分
{
AtomType atom; //原子结点的值域
struct GLNode* hp; //表结点的表头指针
};
struct GLNode* tp; //相当于线性链表的nex七,指向下一个元素结点
}*GList;
第6章 树
一、二叉树
(一)二叉树
1、定义
(1)二叉树
二叉树是 n ( n ≥ 0 ) n(n≥0) n(n≥0)个结点的集合,这个集合可以是空集,或者是由一个根结点和两棵称为左子树和右子树的互不相交的二叉树组成。
特点
(1)可以是空树,即不含任何结点;
(2)每个结点至多只有二棵子树(即二叉树中不存在度大于2的结点);
(3)二叉树是有序树,子树有左右之分,次序不能任意颠倒;允许某些结点只有右子树,或者只有左子树。
(2)满二叉树
如果一个二叉树的叶子结点都在最后一层上,且不存在度数为1的结点,则称该二叉树为满二叉树。
设高为 K K K,则有 2 k − 1 2^k-1 2k−1个结点。
(3)完全二叉树
树的顺序存储结构的基础
如果存在一棵二叉树,对树中的结点自上而下、自左而右连续编号,若编号为i的结点与满二叉树中编号为i的结点的位置相同,则称此二叉树为完全二叉树。
2、性质
(1)在二叉树的第 i ( i ≥ 1 ) i(i≥1) i(i≥1)层上至多有 2 i − 1 2^i-1 2i−1个结点
(2)深度为 k k k的二叉树最多有 2 k − 1 2^k-1 2k−1个结点
(3)对任意二叉树T,如果其终端结点数为 n 0 n_0 n0(度数为0的结点数), n 1 n_1 n1, n 2 n_2 n2分别表示度数为1,2的结点个数,则 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
证明:从两个方面考虑: ①节点总数 ②总度数
①节点总数:设n为二叉树T的结点总数,则有: n = n 0 + n 1 + n 2 n=n_0+n_1+n_2 n=n0+n1+n2
②总度数:总度数为 n − 1 n-1 n−1, n − 1 = n 1 + 2 ∗ n 2 n-1=n_1+2*n_2 n−1=n1+2∗n2
求得: n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
(4)具有n个结点的完全二叉树的深度为 ⌊ l o g 2 n ⌋ + 1 ⌊log_2n⌋+1 ⌊log2n⌋+1或者( ⌈ l o g 2 ( n + 1 ) ⌉ ⌈log2(n+1)⌉ ⌈log2(n+1)⌉)。
证明:设深度为k
①前k-1层的节点总数是2k-1-1
②k层最多有节点是2k-1
∵ 2 k − 1 − 1 < n ≤ 2 k − 1 ∵ 2k-1-1<n≤2k-1 ∵2k−1−1<n≤2k−1
2 k − 1 ≤ n < 2 k 2k-1 ≤n<2k 2k−1≤n<2k
∴ k − 1 ≤ l o g 2 n < k ∴ k-1≤log2n<k ∴k−1≤log2n<k
k = ⌊ l o g 2 n ⌋ + 1 k=⌊log_2n⌋+1 k=⌊log2n⌋+1
(5)对于一个具有n个结点的完全二叉树,其结点按层序编号(从第1层到第 ⌊ l o g 2 n ⌋ + 1 ⌊log_2n⌋+1 ⌊log2n⌋+1层,每层从左到右),则对任一结点 k i ( 1 ≤ i ≤ n ) k_i(1≤i≤n) ki(1≤i≤n),有
①已知编号 i i i,求双亲的编号?
当 1 < i < = n 1<i<=n 1<i<=n时,其双亲节点为 ⌊ i / 2 ⌋ ⌊i/2⌋ ⌊i/2⌋
②已知编号 i i i,求左孩子的编号?
当 1 ≤ i ≤ n / 2 1≤i≤n/2 1≤i≤n/2时,其左孩子节点为 2 i 2i 2i
③已知编号 i i i,求右孩子的编号?
当 1 ≤ i ≤ ( n − 1 ) / 2 1≤i≤(n-1)/2 1≤i≤(n−1)/2时,其右孩子节点为 2 i + 1 2i+1 2i+1
④已知编号 i i i,求左兄弟的编号?
当 i 为 奇 数 且 1 < i ≤ n i为奇数 且 1<i≤n i为奇数且1<i≤n时,其左兄弟节点为 i − 1 i-1 i−1
⑤已知编号 i i i,求右兄弟的编号?
当 i 为 偶 数 且 1 < i < n i为偶数且1<i<n i为偶数且1<i<n时,其右兄弟节点为 i + 1 i+1 i+1
3、存储结构
(1)顺序存储结构
①定义
利用完全二叉树的性质,可以把一个完全二叉树的结点存入向量b[n]中,b[0]存根结点。
代码实现:
#define MAX_TREE_SIZE 100 // 二叉树的最大结点数
typedef char TElemType;//元素数据类型(举例)
typedef TElemType SqBiTree[MAX_TREE_SIZE];// 定义数组数据类型SqBiTree
int main()
{
//调用
SqBiTree bt; // 与TElemType bt[100] 相同。
}
注意:实际应用中可采用:1号单元存储根结点,0号单元存节点的个数。
②操作
Ⅰ 查找类:
Ⅱ 插入类:
Ⅲ 删除类:
(2)链式存储结构(二叉链表)
①定义
typedef struct BiTNode
{
TElemType data; //数据域
struct BiTNode* lchild, * rchild; //左右孩子指针
}BiTNode,*BiTree;
②操作
(二)遍历二叉树
1、四种遍历
(1)先序遍历
void Preorder (BiTree T)
{
if (T)
{
visit(T->data); // 访问结点
Preorder(T->lchild); // 遍历左子树
Preorder(T->rchild); // 遍历右子树
}
}
(2)中序遍历
void Inorder(BiTree T)
{
if (T)
{
Inorder(T->lchild); // 遍历左子树
visit(T->data); // 访问结点
Inorder(T->rchild); // 遍历右子树
}
}
(3)后序遍历
void Postorder(BiTree T)
{
if (T)
{
Postorder(T->lchild); // 遍历左子树
Postorder(T->rchild); // 遍历右子树
visit(T->data); // 访问结点
}
}
(4)层次遍历
#include <queue>
bool LevelOrderTraverse(BiTree T)
{
queue<BiTNode*>Q;
BiTNode* E;
if (T)
Q.push(T);
while (Q.empty())
{
E = Q.front();
Q.pop();
visit(E);
if (E->lchild)
Q.push(E->lchild);
if (E->rchild)
Q.push(E->rchild);
}
}
2、创建二叉树
(1)先序序列
①输入字符
注意:给定先序遍历不能唯一确定一棵二叉树。
要想给定先序遍历确定一棵二叉树,必须把 空子树位置补上。
如由图所示二叉树,输入:
AB#E##CF###
其中:#表示空
void CreateBiTree(BiThrTree& T)//根据先缀串建树
{
char ch;
cin >> ch;
if (ch == '#')
T = NULL;
else
{
T = (BiTNode*)malloc(sizeof(BiTNode));
T->data = ch;
// T->LTag = 0;//默认为0,也可以:T->LTag=(T->lchild==NULL)?1:0;
// T->RTag = 0;//默认为0,也可以:T->RTag=(T->rchild==NULL)?1:0;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
}
②输入数字
//测试案例
11
/ \
22 -33
\
44
11
22
#
-33
#
#
44
#
#
void CreateBiTree(BiTree& T)
{
string ch;
cin >> ch;
if (ch == "#")
T = NULL;
else
{
T = (BiTNode*)malloc(sizeof(BiTNode));
T->data = atoi(ch.c_str());
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
}
(2)中序序列
void CreateBiTree(BiThrTree& T)//根据先缀串建树
{
char ch;
cin >> ch;
if (ch == '#')
T = NULL;
else
{
CreateBiTree(T->lchild);
T = (BiTNode*)malloc(sizeof(BiTNode));
T->data = ch;
CreateBiTree(T->rchild);
}
}
(3)后序序列
void CreateBiTree(BiThrTree& T)//根据先缀串建树
{
char ch;
cin >> ch;
if (ch == '#')
T = NULL;
else
{
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
T = (BiTNode*)malloc(sizeof(BiTNode));
T->data = ch;
}
}
2、还原二叉树
(1)给出一种遍历次序不能唯一确定一棵二叉树
(2)下列两种遍历次序能唯一确定一棵二叉树的是
①先序+后序❌ ②先序+中序✔ ③中序+后序✔
两种搭配要让左右子树的序列能分开
①先序+中序
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/cc6686eb5d9cb8cc6abbc5e432c6fe81.png)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/dd6d758218dc38ace3ba0e19321c1dda.png)
c
//查找下标
int Find_Index(char* s, char ch)
{
int i;
for (i = 0; i <strlen(s); ++i)
{
if (s[i] == ch)
return i;
}
return -1;
}
//前序中序还原二叉树
bool PreInBiTree(BiTree& T, char* s1, char* s2, int i1, int j1, int i2, int j2)
{
if (i1 <= j1 && i2 <= j2)
{
T = (BiTNode*)malloc(sizeof(BiTNode));
if (!T)
{
cout << "申请内存空间失败!" << endl;
return false;
}
T->data = s1[i1];
T->lchild = NULL;
T->rchild = NULL;
int index;
index = Find_Index(s2, s1[i1]);
if (index == -1)
{
cout << "前序中序匹配错误!" << endl;
return false;
}
PreInBiTree(T->lchild, s1, s2, i1 + 1, i1 + index - i2, i2, index - 1);//建立左子树
PreInBiTree(T->rchild, s1, s2, i1 + index + 1 - i2, j1, index + 1, j2);//建立右子树
return true;
}
return false;
}
//创建二叉树
void CreateBiTree(BiTree& T)
{
cout << "结点总数:";
int n;
cin >> n;
char* s1, * s2;
s1 = (char*)malloc(n * sizeof(char));
s2 = (char*)malloc(n * sizeof(char));
cout << "先序输入:";
cin >> s1;
cout << "中序输入:";
cin >> s2;
PreInBiTree(T, s1, s2, 0, n - 1, 0, n - 1);
}
cpp
//前序中序还原二叉树
bool PreInBiTree(BiTree& T, string s1, string s2)
{
if (!s1.empty() && !s2.empty())
{
T = (BiTNode*)malloc(sizeof(BiTNode));
if (!T)
{
cout << "申请内存空间失败!" << endl;
return false;
}
T->data = s1[0];
T->lchild = NULL;
T->rchild = NULL;
string::size_type index;
index = s1.find(s1[0]);
if (index == string::npos)
{
cout << "前序中序不匹配!" << endl;
return false;
}
PreInBiTree(T->lchild, s1.substr(1, index), s2.substr(0, index));//建立左子树
PreInBiTree(T->rchild, s1.substr(index + 1, s1.length() - index), s2.substr(index + 1, s2.length() - index));//建立右子树
return true;
}
return false;
}
//创建二叉树
bool CreateBiTree(BiTree& T)
{
cout << "结点总数:";
int n;
cin >> n;
if (n == 0)
{
cout << "节点总数为0,不合法!" << endl;
return false;
}
string s1, s2;
cout << "先序输入:";
cin >> s1;
cout << "中序输入:";
cin >> s2;
PreInBiTree(T, s1, s2);
return true;
}
②中序+后序
4、利用遍历解决问题
(1)求二叉树高度
int height(BiTree T)
{
int tl, tr;
if (T == NULL)
return 0;
tl = height(T->lchild);
tr = height(T->rchild);
return tl >= tr ? tl + 1 : tr + 1; //比较左右子树哪个高
}
(2)计算二叉树结点个数(递归算法)
中序遍历
int c = 0;//要递归函数中定义技术的变量要设置成全局变量,否则会重复定义初始化
void count(BiTree T)
{
if (T != NULL)
{
count(T->lchild); //先左子树上有多少结点
++c; //根结点+1
count(T->rchild); //再右子树结点个数
}
}
(3)计算二叉树叶子结点个数(递归算法)
中序遍历
int c = 0;//要递归函数中定义技术的变量要设置成全局变量,否则会重复定义初始化
void count(BiTree T)
{
if (T != NULL)
{
count(T->lchild);
if (T->lchild == NULL && T->rchild == NULL)//判断是否为叶子结点
++c;
count(T->rchild);
}
}
(4)删除(销毁)以二叉链表做存储结构的树T(递归算法)
删除二叉树只能用后序遍历——最后才能把根删了!!!
void DestroyBiTree(BiTree& T)
{
if (T)
{
DestroyBiTree(T->lchild);
DestroyBiTree(T->rchild);
free(T);//!!!
}
}
5、中序遍历非递归算法
void NINORDER(BiTree T)
{
stack<BiTNode*>S;
BiTNode* e;
//找最左结点
while (T)//一直往下找,直到T为空
{
S.push(T);
T = T->lchild;
}
//遍历
while (!S.empty())//若不空
{
e = S.top();//D出栈
S.pop();
visit(e);//先访问D
e = e->rchild;//再指向右子树
while (e != NULL)//e不空
{
S.push(e);//右子树左孩子压栈
e = e->lchild;//找左孩子
}
}
}
(三)线索二叉树
1、线索二叉树结点类型定义
(1)结点结构
(2)代码
typedef struct BiThrNode
{
TElemType data; //数据元素
struct BiThrNode* lchild, * rchild; //左右指针
int ltag, rtag;
}BiThrNode, * BiThrTree;
2、中序线索化
(1)建树
//根据先缀串建树
void CreateBiThrTree(BiThrTree& T)
{
char ch;
cin >> ch;
if (ch == '#')
T = NULL;
else
{
T = (BiThrNode*)malloc(sizeof(BiThrNode));
T->data = ch;
T->ltag = 0;//默认为0——指针;1——线索
T->rtag = 0;
CreateBiThrTree(T->lchild);
CreateBiThrTree(T->rchild);
}
}
(2)不带头结点线索化
①思想
②代码实现
//中序线索化——不带头结点
BiThrTree pre = NULL;
void InOrderThreading(BiThrTree p)
{
if (p != NULL)
{
InOrderThreading(p->lchild);
//更改左右标志
if (p->lchild == NULL)
p->ltag = 1;//改为线索
else
p->ltag = 0;
if (p->rchild == NULL)
p->rtag = 1;
else
p->rtag = 0;
//线索左右空结点
if (pre != NULL)//前驱结点非空
{
if (pre->rtag == 1)//前驱pre有无右孩子
pre->rchild = p;
}
if (p->ltag == 1)//p有无左孩子
p->lchild = pre;
pre = p;//前驱结点往后走一步
InOrderThreading(p->rchild);
}
}
(3)带头结点线索化
①定义
②代码
//全局变量
BiThrNode* pre = NULL;
//中序线索化
void InThreading(BiThrTree p)
{
if (p)//对以p为根的非空二叉树进行中序线索化
{
InThreading(p->lchild);//左子树线索化
//更改左右标志并线索
//建前驱线索
if (!p->lchild)
{
p->ltag = 1;//线索
p->lchild = pre;//左链指向前驱结点
}
else
p->ltag = 0;//指针,左链默认就是左孩子
//建后继线索
if (!pre->rchild)
{
pre->rtag = 1;//线索
pre->rchild = p;//右链指向后继结点
}
else
pre->rtag = 0;//指针,右链默认就是右孩子
pre = p;//保持pre指向p的前驱
InThreading(p->rchild);
}
}
//头结点初始化
bool InOrderThreading(BiThrTree& Thrt, BiThrTree T)
{
//添加头结点
Thrt = (BiThrNode*)malloc(sizeof(BiThrNode));
if (!Thrt)
{
cout << "申请头结点失败!" << endl;
return false;
}
//初始化头结点↓
Thrt->ltag = 0;
Thrt->rtag = 1;
Thrt->rchild = Thrt;//一开始头结点右链指向自身,最后线索后才只想最后一个结点
if (!T)
Thrt->lchild = Thrt;
else
{
Thrt->lchild = T;
//中序线索化↓
pre = Thrt;
InThreading(T);
//处理最后一个结点的右链
pre->rchild = Thrt;//树T中最后一个结点右孩子没有初始化,要指向头结点
pre->rtag = 1;//线索
Thrt->rchild = pre;//头结点的右链要指向原来树T中最后一个结点
}
}
3、查找前驱后继
(1)中序线索树
①前驱
BiThrTree Inorderpre(BiThrTree p)
{
BiThrNode* q;
if (p->ltag == 1)//无左子树
return p->lchild;
else//有左子树
{
//找左子树最右结点
q = p->lchild;
while (q->rtag == 0)
q = q->rchild;
return q;
}
}
②后继
BiThrNode* Inordernext(BiThrNode* p)
{
BiThrNode* q;
if (p->rtag == 1)//无右子树(孩子)
return p->rchild;
else
{
q = p->rchild;//q指向p的右孩子
//一直往左找,找最左节点
while (q->ltag == 0)
q = q->lchild;
return q;
}
}
(2)先序线索树
①前驱
②后继
BiThrNode* Preordernext(BiThrNode* p)
{
if (p->ltag == 0)//有左孩子
return p->lchild;
else//有右子树无左孩子+没有右子树
return p->rchild;
}
(3)后序线索树
①前驱
BiThrNode* Postordernext(BiThrNode* p)
{
if (p->rtag == 0)//有左左孩子
return p->rchild;
else//有右子树无左孩子+没有右子树
return p->lchild;
}
②后继
二、树和森林
(一)树的存储结构
1、双亲表示法
#define MAX_TREE_SIZE 100
//每个结点的数据类型
typedef struct PTNode
{
ElemType data;
int parent; //父节点位置域
}PTNode;
//树的数据类型
typedef struct
{
PTNode nodes[MAX_TREE_SIZE];
int r, n; //根结点位置,结点总数
}PTree;
2、孩子链表示法
#define MAX_TREE_SIZE 100
//孩子链表结点
typedef struct CTNode
{
int child;
struct CTNode* next;
}CTNode,*ChildPtr;
//双亲(父)结点结构
typedef struct
{
ElemType data;
ChildPtr firstchild;//孩子链的头指针
}CTBox;
//树结构
typedef struct
{
CTBox nodes[MAX_TREE_SIZE];//结构体数组
int n, r;//结点数和根结点的位置
}CTree;
3、树的二叉链表存储表示法(孩子兄弟表示法)
typedef struct CSNode
{
ElemType data;
struct CSNode* firstchild, * nextchild;
}CSNode,*CSTree;
(二)森林与二叉树的转换
见PPT
三、赫夫曼树及其应用
(一)最优二叉树(赫夫曼树)和赫夫曼编码
1、赫夫曼树和赫夫曼编码的存储表示
//赫夫曼树存储在结构体数组里
typedef struct
{
unsigned int weight;
unsigned int parent, lchild, rchild;
}HTNode, *HuffmanTree;
//赫夫曼编码——字符数组的指针
typedef char** HuffmanCode;
2、赫夫曼树(最优二叉树)的构建
①HuffmanTreeing
//构建赫夫曼树
void HuffmanTreeing(HuffmanTree& HT, float* w, int n)
//赫夫曼树,权重,n个叶子结点
{
if (n <= 1)//小于等于1不需要建立
return;
int m;
m = 2 * n - 1;//构造出的赫夫曼树有2n-1个结点!!!
HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));//树结构体数组申请动态存储空间
//m+1:0号单元不存
HTNode* p;
int i;
for (p = HT, i = 1; i <= n; ++i, ++p, ++w)//结构体数组初始化,前n个叶子结点有权重,其余为0
{
//初始化
p->weight = *w;
p->parent = 0;
p->lchild = 0;
p->rchild = 0;
}
for (; i <= m; ++i, ++p)//后面的结点也初始化为0
{
//初始化
p->weight = 0;
p->parent = 0;
p->lchild = 0;
p->rchild = 0;
}
for (i = m + 1; i <= m; ++i)//n+1到m求每个节点权重
{
int s1, s2;
Select(HT, i - 1, s1, s2);//求两个最小值的位置s1和s2
//在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的结点,
// 并返回它们在HT中的序号s1和s2
//得到新结点i,从森林中删除s1,s2,将s1和s2的双亲域由0改为i
HT[s1].parent = i;
HT[s2].parent = i;
//s1,s2分别作为i的左右孩子
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;//i 的权值为左右孩子权值之和
}
}
②Select
#define MAX_NUM 1000
void Select(HuffmanTree HT, int len, int& s1, int& s2)
{
int i, min1 = MAX_NUM, min2 = MAX_NUM;//先赋予最大值
for (i = 1; i <= len; i++)//在HT[k](1≤k≤i-1)中选择其双亲域为0且权值最小的第1个结点
{
if (HT[i].weight < min1 && HT[i].parent == 0)
{
min1 = HT[i].weight;
s1 = i;
}
}
int temp = HT[s1].weight;//将原值存放起来,然后先赋予最大值,防止s1被重复选择!!!!!!!!!!!
HT[s1].weight = MAX_NUM;
for (i = 1; i <= len; i++)//在HT[k](1≤k≤i-1)中选择其双亲域为0且权值最小的第2个结点
{
if (HT[i].weight < min2 && HT[i].parent == 0)
{
min2 = HT[i].weight;
s2 = i;
}
}
HT[s1].weight = temp;//恢复原来的值!!!!!!!!
}
3、赫夫曼编码
a. 首先构造出赫夫曼树:
b. 设计赫夫曼编码:在有了赫夫曼树之后,将树中每个结点的左分支用“0”代表,右分支用“1”代表。则从根结点到叶结点之间,沿途路径上的分支组成的“0”或“1”代码串就是该叶子结点所代表的字符编码,称为赫夫曼编码。
//赫夫曼编码
void HuffmanCoding(HuffmanTree& HT, HuffmanCode& HC, float* w, int n)
//赫夫曼树,赫夫曼编码,权重,n个叶子结点
{
HC = (HuffmanCode)malloc((n + 1) * sizeof(char*));//码指针数组申请存储空间
//0号单元未用
char* cd;
cd = (char*)malloc(n * sizeof(char));//临时存储空间→字符数组
//编码n-1位够用,最后一位'\0'
cd[n - 1] = '\0';//编码结束符
int i, start;
for (i = 1; i <= n; ++i)
{
start = n - 1;//编码结束符位置
//我们是反这求编码,开始是从n-1开始
int c, f;
for (c = i, f = HT[i].parent; f != 0; c = f)//父节点f不为0;c:父节点编父父
{
if (HT[i].lchild == c)
cd[--start] = '0';//左0
else
cd[--start] = '1';
}
HC[i] = (char*)malloc((n - start) * sizeof(char));//设计一个相应大小的赫夫曼编码
strcpy(HC[i], &cd[start]);//复制临时编码到这里
}
free(cd);//释放临时工作空间
}
第7章 图
一、图的基本操作
二、图的存储结构
统一实现
①头文件,预定义、元素数据类型定义
#define INFINITY 1000 //最大值
#define MAX_VERTEX_NUM 100 //最大顶点个数
typedef int VRType; //顶点关系数据类型
typedef int InfoType; //其他额外信息数据类型
typedef int VertexType;; //顶点数据类型
typedef enum { DG, DN, UDG, UDN } GraphKind;//图的种类
②边(邻接矩阵)的定义
typedef struct ArcCell
{
VRType adj; //VRType是顶点关系类型
InfoType* info; //该弧相关信息的指针(其他额外信息)
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
③图的定义
typedef struct
{
VertexType; vexs[MAX_VERTEX_NUM];//顶点信息
AdjMatrix arcs; //边的信息
int vexnum, arcnum; //顶点数,边数
GraphKind kind; //图的种类标志
};
建图
(一)邻接矩阵
1、无向图
(1)定义
(2)特点
①是对称矩阵
②对角线元素都是零
(3)求解
①无向图中第i个顶点的度在邻接矩阵中如何体现?
第i行(或第i列)的非零元素个数之和
②图的总度数怎么求?
非零元个数之和
③边数怎么求?
边数为总度数的一半
(4)实现
①预定义、元素数据类型定义
#define MAX_VERTEX_NUM 100 //最大顶点个数
typedef int VRType; //顶点关系数据类型
typedef int InfoType; //其他额外信息数据类型
typedef int VertexType; //顶点数据类型
②边(邻接矩阵)的定义
typedef struct ArcCell
{
VRType adj; //VRType是顶点关系类型
InfoType* info; //该边相关信息的指针(其他额外信息)
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
③图的定义
typedef struct
{
VertexType; vexs[MAX_VERTEX_NUM];//顶点信息
AdjMatrix arcs; //边的信息
int vexnum, arcnum; //顶点数,边数
}MGraph;
(5)建图
void CreateGraph(MGraph& G)
{
int i, j, k;
int w;
cout << "输入:顶点数 边数" << endl;
cin >> G.vexnum;
cin >> G.arcnum;
for (i = 1; i <= G.vexnum; ++i)//顶点一维数组赋值
cin >> G.vexs[i];
//邻接矩阵初始化
for (i = 1; i <= G.vexnum; ++i)
{
for (j = 1; j <= G.vexnum; ++j)
{
G.arcs[i][j].adj = 0;//区别
G.arcs[i][j].info = NULL;
}
}
//输入数据
for (k = 1; k <= G.arcnum; ++k)
{
cout << "输入:顶点1 顶点2 权重" << endl;
cin >> i;
cin >> j;
cin >> w;
G.arcs[i][j].adj = w;
G.arcs[j][i].adj = w;//区别
}
}
2、有向图
(1)定义
(2)特点
①不一定是对称矩阵
②对角线元素都是零
(3)求解
①有向图中第i个顶点的出度和入度在邻接矩阵中如何体现?
第i行的非零元个数之和;
第i列的非零元个数之和;
②边数是多少?
边数为非零元的个数。
(4)实现
①预定义、元素数据类型定义
#define MAX_VERTEX_NUM 100 //最大顶点个数
typedef int VRType; //顶点关系数据类型
typedef int InfoType; //其他额外信息数据类型
typedef int VertexType;; //顶点数据类型
②弧(邻接矩阵)的定义
typedef struct ArcCell
{
VRType adj; //VRType是顶点关系类型
InfoType* info; //该弧相关信息的指针(其他额外信息)
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
③图的定义
typedef struct
{
VertexType; vexs[MAX_VERTEX_NUM];//顶点信息
AdjMatrix arcs; //弧的信息
int vexnum, arcnum; //顶点数,弧数
}MGraph;
(5)建图
void CreateGraph(MGraph& G)
{
int i, j, k;
int w;
cout << "输入:顶点数 边数" << endl;
cin >> G.vexnum;
cin >> G.arcnum;
for (i = 1; i <= G.vexnum; ++i)//顶点一维数组赋值
cin >> G.vexs[i];
//邻接矩阵初始化
for (i = 1; i <= G.vexnum; ++i)
{
for (j = 1; j <= G.vexnum; ++j)
{
G.arcs[i][j].adj = 0;//区别
G.arcs[i][j].info = NULL;
}
}
//输入数据
for (k = 1; k <= G.arcnum; ++k)
{
cout << "输入:顶点1 顶点2 权重" << endl;
cin >> i;
cin >> j;
cin >> w;
G.arcs[i][j].adj = w;//区别
}
}
3、网络(以有向网络为例)
(1)定义
无穷大在实际应用中怎么表示?
用一个特殊的数表示(可用某范围内的最大值)
(2)特点
①不一定是对称矩阵
②对角线元素都是最大值
(3)求解
①有向图中第i个顶点的出度和入度在邻接矩阵中如何体现?
第i行的非无穷大元个数之和;
第i列的非无穷大元个数之和;
②边数是多少?
边数为非无穷大元的个数。
(4)实现
①预定义、元素数据类型定义
#define INFINITY 1000 //最大值!!!
#define MAX_VERTEX_NUM 100 //最大顶点个数
typedef int VRType; //顶点关系数据类型
typedef int InfoType; //其他额外信息数据类型
typedef int VertexType;; //顶点数据类型
②弧(邻接矩阵)的定义
typedef struct ArcCell
{
VRType adj; //VRType是顶点关系类型
InfoType* info; //该弧相关信息的指针(其他额外信息)
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
③图的定义
typedef struct
{
VertexType; vexs[MAX_VERTEX_NUM];//顶点信息
AdjMatrix arcs; //弧的信息
int vexnum, arcnum; //顶点数,弧数
GraphKind kind; //图的种类标志
}MGraph;
(5)建图
①无向网络
void CreateGraph(MGraph& G)
{
int i, j, k;
int w;
cout << "输入:顶点数 边数" << endl;
cin >> G.vexnum;
cin >> G.arcnum;
for (i = 1; i <= G.vexnum; ++i)//顶点一维数组赋值
cin >> G.vexs[i];
//邻接矩阵初始化
for (i = 1; i <= G.vexnum; ++i)
{
for (j = 1; j <= G.vexnum; ++j)
{
G.arcs[i][j].adj = INFINITY;//区别
G.arcs[i][j].info = NULL;
}
}
//输入数据
for (k = 1; k <= G.arcnum; ++k)
{
cout << "输入:顶点1 顶点2 权重" << endl;
cin >> i;
cin >> j;
cin >> w;
G.arcs[i][j].adj = w;
G.arcs[j][i].adj = w;//区别
}
}
②有向网络
void CreateGraph(MGraph& G)
{
int i, j, k;
int w;
cout << "输入:顶点数 边数" << endl;
cin >> G.vexnum;
cin >> G.arcnum;
for (i = 1; i <= G.vexnum; ++i)//顶点一维数组赋值
G.vexs[i] = getchar();
//邻接矩阵初始化
for (i = 1; i <= G.vexnum; ++i)
{
for (j = 1; j <= G.vexnum; ++j)
{
G.arcs[i][j].adj = INFINITY;//区别
G.arcs[i][j].info = NULL;
}
}
//输入数据
for (k = 1; k <= G.arcnum; ++k)
{
cout << "输入:顶点1 顶点2 权重" << endl;
cin >> i;
cin >> j;
cin >> w;
G.arcs[i][j].adj = w;//区别
}
}
特点
时间复杂度
O ( n 2 ) O(n^2) O(n2)
操作特点
边或弧的删除和插入操作容易。顶点的插入删除操作不容易。
(二)邻接表
统一实现
实现
建图
1、无向图
(1)定义
①预定义、元素数据类型定义
#define MAX_VERTEX_NUM 20 //最大顶点个数
typedef int InfoType; //其他额外信息数据类型
typedef int vertextype;//顶点信息数据类型
②边表存储类型
typedef struct ArcNode
{
int adjvex; //该边指向的顶点的位置(边的另一个顶点)
struct ArcNode* nextarc;//指向下一条边的指针
InfoType* info; //其他额外信息
}ArcNode;
③顶点表存储类型
typedef struct vnode
{
vertextype data; //顶点信息
ArcNode* firstarc; //顶点指向边表
}vnode, adjlist[MAX_VERTEX_NUM];
④图的邻接表存储类型
typedef struct
{
adjlist vertices; //存顶点表(边的信息实际上是在顶点表上的)
int vexnum, arcnum; //顶点数,边数
}ALGraph;
(2)建无向图
void Createadjlist(ALGraph& G)
{
int i, j, k;
ArcNode* s;
cout << "输入:顶点数 边数" << endl;
cin >> G.vexnum;
cin >> G.arcnum;
//建立顶点
for (i = 1; i <= G.vexnum; ++i)
{
cin >> G.vertices[i].data;
G.vertices[i].firstarc = NULL;
}
//输入数据
for (k = 0; k < G.arcnum; ++k)
{
cout << "输入:顶点1 顶点2" << endl;
cin >> i;
cin >> j;
//插入第一个
s = (ArcNode*)malloc(sizeof(ArcNode));
s->adjvex = j;
s->info = NULL;
//前插法
s->nextarc = G.vertices[i].firstarc;
G.vertices[i].firstarc = s;
//插入第二个——区别
s = (ArcNode*)malloc(sizeof(ArcNode));
s->adjvex = i;
s->info = NULL;
//前插法
s->nextarc = G.vertices[j].firstarc;
G.vertices[j].firstarc = s;
}
}
2、有向图
(1)定义
Ⅰ出边表
①预定义、元素数据类型定义
#define MAX_VERTEX_NUM 20 //最大顶点个数
typedef int InfoType; //其他额外信息数据类型
typedef int vertextype;//顶点信息数据类型
②出边表存储类型
typedef struct ArcNode
{
int adjvex; //该弧指向的顶点的位置(弧的另一个顶点)
struct ArcNode* nextarc;//指向下一条弧的指针
InfoType* info; //其他额外信息
}ArcNode;
③顶点表存储类型
typedef struct vnode
{
vertextype data; //顶点信息
ArcNode* firstarc; //顶点指向边表
}vnode, adjlist[MAX_VERTEX_NUM];
④图的邻接表存储类型
typedef struct
{
adjlist vertices; //存顶点表(边的信息实际上是在顶点表上的)
int vexnum, arcnum; //顶点数,弧数
}ALGraph;
(2)建图(出边表)
void Createadjlist(ALGraph& G)
{
int i, j, k;
ArcNode* s;
cout << "输入:顶点数 边数" << endl;
cin >> G.vexnum;
cin >> G.arcnum;
//建立顶点
for (i = 1; i <= G.vexnum; ++i)
{
cin >> G.vertices[i].data;
G.vertices[i].firstarc = NULL;
}
//输入数据
for (k = 0; k < G.arcnum; ++k)
{
cout << "输入:顶点1->顶点2" << endl;
cin >> i;
cin >> j;
//插入
s = (ArcNode*)malloc(sizeof(ArcNode));
s->adjvex = j;
s->info = NULL;
//前插法
s->nextarc = G.vertices[i].firstarc;
G.vertices[i].firstarc = s;
}
}
Ⅱ入边表——逆邻接表
(三)十字链表——存储有向图
1、定义
2、代码
①预定义,元素数据类型定义
#define MAX_VERTEX_NUM 20 //最大顶点个数
typedef int InfoType; //额外信息数据类型
typedef int VertexType;;//顶点信息数据类型
②边表的存储类型
typedef struct ArcBox
{
int tailvex, headvex;//弧尾,弧头
struct ArcBox* hlink, * tlink;//分别为弧头相同和弧尾相同的弧的链域
InfoType* info; //该弧相关信息的指针
}ArcBox;
③顶点表的存储类型
typedef struct VexNode
{
VertexType; data;//顶点信息
ArcBox* firstin, * Firstout;//指向第一条依附该顶点的弧
}VexNode;
④图的十字链表存储类型
typedef struct
{
VexNode xlist[MAX_VERTEX_NUM];//有顶点构成的结构体数组
int vexnum, arcnum; //顶点数,弧数
}OLGraph;
三、图的遍历
(一)深度优先搜索
1、思想
类似于先序遍历
分治思想
①首先访问图中某一个顶点
V
i
V_i
Vi,以该顶点为出发点;
②任选一个与顶点Vi邻接的未被访问的顶点
V
j
V_j
Vj;访问
V
j
V_j
Vj;
③以
V
j
V_j
Vj为新的出发点继续进行深度优先搜索,直至图中所有和
V
i
V_i
Vi有路径的顶点均被访问到。
2、实现
输入:图G,初始访问节点v;
输出:图G的深度优先搜索序列;
①访问v;
②改变v的访问标志;
③任选一个与v相邻又没被访问的顶点w;
④从w开始继续进行深度优先搜索。
(1)邻接矩阵
①邻接矩阵数据结构(预定义、元素数据类型)
#define MAX_VERTEX_NUM 20 //最大顶点个数(递归的话不要设置太大)
typedef char VertexType;;
typedef int VRType, InfoType;
//边的定义
typedef struct ArcCell
{
VRType adj; //顶点间关系值(矩阵中的数)
InfoType* info; //额外信息
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
//图的定义
typedef struct
{
VertexType; vexs[MAX_VERTEX_NUM];//顶点信息
AdjMatrix arcs; //邻接矩阵
int vexnum, arcnum; //顶点数,边数
}MGraph;
②DFS算法
//深度优先算法
bool visited[MAX_VERTEX_NUM];
void DFS(MGraph G, int v)
{
// 从顶点v出发,深度优先搜索遍历连通图 G
visited[v] = true;
cout << G.vexs[v] << endl;//访问v
int w;
for (w = 0; w < G.vexnum; ++w)
{
if (G.arcs[v][w].adj != 0 && !visited[w])//检查所有顶点是否与v有边(看列)
DFS(G, w);//递归调用DFS
}
}
③主程序
//深度优先搜索主程序
void DFSTraverse(MGraph G, int v)
{
for (v = 1; v <= G.vexnum; ++v)
visited[v] = false; //访问标志数组初始化
for (v = 1; v <= G.vexnum; ++v)//使得非连通图也适用
{
if (!visited[v])//如果一次深度遍历没遍历全会让他进行二次遍历
DFS(G, v);
}
}
(2)邻接表
①邻接表数据结构(预定义、元素数据类型)
#define MAX_VERTEX_NUM 20
typedef int InfoType, VertexType;;
//边表
typedef struct ArcNode
{
int adjvex; //边的另一个顶点
struct ArcNode* nextarc;//指向下一条边的指针
InfoType* info;
}ArcNode;
//顶点表
typedef struct vnode
{
VertexType; data; //顶点信息
ArcNode* firstarc; //顶点指向边表
}vnode,adjlist[MAX_VERTEX_NUM];
//图
typedef struct
{
adjlist vertices; //存顶点表
int vexnum, arcnum; //顶点数,边数
}ALGraph;
②DFS算法
//深度优先遍历(邻接表)
bool visited[MAX_VERTEX_NUM];
void DFS(ALGraph G, int v)
{
visited[v] = true;
cout << G.vertices[v].data << endl;
int i;
ArcNode* p;
p = G.vertices[v].firstarc;
while (p != NULL)
{
if (!visited[p->adjvex])
DFS(G, p->adjvex);
p = p->nextarc;
}
}
③主程序
//深度优先搜索主程序
void DFSTraverse(ALGraph G, int v)
{
for (v = 1; v <= G.vexnum; ++v)
visited[v] = false; //访问标志数组初始化
for (v = 1; v <= G.vexnum; ++v)//使得非连通图也适用
{
if (!visited[v])//如果一次深度遍历没遍历全会让他进行二次遍历
DFS(G, v);
}
}
3、算法分析
(二)广度优先搜索
1、思想
广度优先搜索(Breadth First Search)遍历类似于树的按层次遍历。
①首先访问图中某一个指定的出发点vi;
②然后依次访问
v
i
v_i
vi的所有邻接点
v
i
1
,
v
i
2
…
v
i
t
v_i1,v_i2…v_it
vi1,vi2…vit;
③再依次以
v
i
1
,
v
i
2
…
v
i
t
v_i1,v_i2…v_it
vi1,vi2…vit为顶点,访问各顶点未被访问的邻接点,依此类推,直到图中所有顶点均被访问为止。
2、实现
输入:图G,初始访问节点v;
输出:图G的广度优先搜索序列;
①设置辅助队列Q, 访问节点v, 修改v的标志,v入队列;
②
while(!Isempty(Q) ) {
出队列节点u;
访问与u临接的所有节点;
修改与u邻接的所有结点的标志;
与u邻接的所有节点入队列;
}
(1)邻接表
①邻接表的数据结构(头文件,预定义,元素数据类型定义)
#include <queue>
#define MAX_VERTEX_NUM 20
typedef int InfoType, VertexType;;
//边表
typedef struct ArcNode
{
int adjvex; //边的另一个顶点
struct ArcNode* nextarc;//指向下一条边的指针
InfoType* info;
}ArcNode;
//顶点表
typedef struct vnode
{
VertexType; data; //顶点信息
ArcNode* firstarc; //顶点指向边表
}vnode, adjlist[MAX_VERTEX_NUM];
//图
typedef struct
{
adjlist vertices; //存顶点表
int vexnum, arcnum; //顶点数,边数
}ALGraph;
②主程序
//广度优先搜索算法(邻接表)
bool visited[MAX_VERTEX_NUM];
void BFSTraverse(ALGraph G, int v)
{
for (v = 1; v < G.vexnum; ++v)
visited[v] = false; //访问标志数组初始化
queue<int>Q; //置空的辅助队列Q
for (v = 1; v <= G.vexnum; ++v) //使得非连通图也适用
{
if (!visited[v])//如果一次广度遍历没遍历全会让他进行二次遍历
{
visited[v] = true;
cout << G.vertices[v].data << endl; //访问v
Q.push(v);//要访问第一个顶点v入队列
ArcNode* p;
int u;
while (!Q.empty())
{
u = Q.front();
Q.pop();//队头元素出队并置为u
for (p = G.vertices[u].firstarc; p != NULL; p = p->nextarc)
{
if (!visited[p->adjvex])//如果没被访问过
{
visited[p->adjvex] = true;
cout << p->adjvex << endl;
//或者写成cout << G.vertices[p->adjvex].data << endl;
Q.push(p->adjvex); //访问的顶点入队列
}
}
}
}
}
}
(2)邻接矩阵
①邻接矩阵的数据结构(预定义,元素数据类型定义)
#include <queue>
#define MAX_VERTEX_NUM 20 //最大顶点个数
typedef int VertexType;;
typedef int VRType, InfoType;
//边的存储
typedef struct ArcCell
{
VRType adj; //顶点间关系值(矩阵中的数)
InfoType* info; //额外信息
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
//图的存储
typedef struct
{
VertexType; vexs[MAX_VERTEX_NUM];//顶点信息
AdjMatrix arcs; //邻接矩阵
int vexnum, arcnum; //顶点数,边数
}MGraph;
②主程序
//广度优先搜索算法(邻接矩阵)
bool visited[MAX_VERTEX_NUM];
void BFSTrverse(MGraph G, int v)
{
for (v = 1; v <= G.vexnum; ++v)
visited[v] = false; //访问标志数组初始化
queue<int>Q; //置空的辅助队列Q
for (v = 1; v <= G.vexnum; ++v) //使得非连通图也适用
{
if (!visited[v])//如果一次广度遍历没遍历全会让他进行二次遍历
{
visited[v] = true;
cout << G.vexs[v] << endl; //访问v
Q.push(v);//要访问第一个顶点v入队列
int u;
while (!Q.empty())
{
u = Q.front();
Q.pop();//队头元素出队并置为u
int i;
for (i = 1; i <= G.vexnum; ++i)
{
if (G.arcs[u][i].adj != 0 && !visited[i])//如果有这条边且没被访问过
{
visited[i] = true;
cout << G.vexs[i] << endl;
Q.push(i); //访问的顶点入队列
}
}
}
}
}
}
3、算法分析
(三)图的遍历与树的遍历
四、图的连通性
(一)无向图的连通分量
1、思想
深度优先搜索和广度优先搜索在基于一点的搜索过程中,能访问到该顶点所在的最大连通子图的所有顶点,即一个连通分量。
通过变换搜索顶点,可求得无向图的所有连通分量。
2、实现(采用深度优先搜索)
(1)邻接矩阵
①邻接矩阵数据结构(预定义、元素数据类型)
#define MAX_VERTEX_NUM 20 //最大顶点个数(递归的话不要设置太大)
typedef char VertexType;;
typedef int VRType, InfoType;
//边的定义
typedef struct ArcCell
{
VRType adj; //顶点间关系值(矩阵中的数)
InfoType* info; //额外信息
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
//图的定义
typedef struct
{
VertexType; vexs[MAX_VERTEX_NUM];//顶点信息
AdjMatrix arcs; //邻接矩阵
int vexnum, arcnum; //顶点数,边数
}MGraph;
②DFS算法
//深度优先算法
bool visited[MAX_VERTEX_NUM];
void DFS(MGraph G, int v)
{
// 从顶点v出发,深度优先搜索遍历连通图 G
visited[v] = true;
cout << G.vexs[v] << endl;//访问v
int w;
for (w = 0; w < G.vexnum; ++w)
{
if (G.arcs[v][w].adj != 0 && !visited[w])//检查所有顶点是否与v有边(看列)
DFS(G, w);//递归调用DFS
}
}
③主程序
//深度优先搜索主程序
void DFSTraverse(MGraph G, int v)
{
int i = 1;//区别!!!
for (v = 1; v <= G.vexnum; ++v)
visited[v] = false; //访问标志数组初始化
for (v = 1; v <= G.vexnum; ++v)//使得非连通图也适用
{
if (!visited[v])//如果一次深度遍历没遍历全会让他进行二次遍历
{
cout << "第" << i << "个连通分量" << endl;//区别!!!
++i;//区别!!!
DFS(G, v)
}
}
}
(2)邻接表
①邻接表数据结构(预定义、元素数据类型)
#define MAX_VERTEX_NUM 20
typedef int InfoType, VertexType;;
//边表
typedef struct ArcNode
{
int adjvex; //边的另一个顶点
struct ArcNode* nextarc;//指向下一条边的指针
InfoType* info;
}ArcNode;
//顶点表
typedef struct vnode
{
VertexType; data; //顶点信息
ArcNode* firstarc; //顶点指向边表
}vnode,adjlist[MAX_VERTEX_NUM];
//图
typedef struct
{
adjlist vertices; //存顶点表
int vexnum, arcnum; //顶点数,边数
}ALGraph;
②DFS算法
//深度优先遍历(邻接表)
bool visited[MAX_VERTEX_NUM];
void DFS(ALGraph G, int v)
{
visited[v] = true;
cout << G.vertices[v].data << endl;
int i;
ArcNode* p;
p = G.vertices[v].firstarc;
while (p != NULL)
{
if (!visited[p->adjvex])
DFS(G, p->adjvex);
p = p->nextarc;
}
}
③主程序
//深度优先搜索主程序
void DFSTraverse(ALGraph G, int v)
{
int i = 1;//区别!!!
for (v = 1; v <= G.vexnum; ++v)
visited[v] = false; //访问标志数组初始化
for (v = 1; v <= G.vexnum; ++v)//使得非连通图也适用
{
if (!visited[v])//如果一次深度遍历没遍历全会让他进行二次遍历
{
cout << "第" << i << "个连通分量" << endl;//区别!!!
++i;//区别!!!
DFS(G, v);
}
}
}
(二)最小生成树
1、定义
对于边带权图(网)来说:在所有的生成树中,各边的权值(边长)总和最小的生成树称作最小生成树。
2、性质
3、算法
(1)Prim算法
①思想
②实现(邻接矩阵)
Ⅰ邻接矩阵数据结构(预定义、元素数据类型)
#define MAX_VERTEX_NUM 20 //最大顶点个数(递归的话不要设置太大)
#define MAX 32767 //表示极大值,即∞
typedef int VertexType;
typedef int VRType, InfoType;
//边的定义
typedef struct ArcCell
{
VRType adj; //顶点间关系值(权值)(矩阵中的数)
InfoType* info; //额外信息
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
//图的定义
typedef struct
{
VertexType vexs[MAX_VERTEX_NUM];//顶点信息
AdjMatrix arcs; //邻接矩阵
int vexnum, arcnum; //顶点数,边数
}MGraph;
Ⅱ主程序
closedge权值数组
//closedage权值数组(直接声明并定义)!!!
struct
{
VertexType adjvex; //U集中的顶点序号
VRType lowcost; //边的权值
}closedge[MAX_VERTEX_NUM];
确定点u在G中的位置
//确定点u在G中的位置
int LocateVex(MGraph G, VertexType u)
{
int i;
for (i = 1; i <= G.vexnum; ++i)
if (G.vexs[i] == u)
return i;
return -1;
}
返回权值最小的点
//返回权值最小的点
int Minimum(MGraph G)
{
int i;
int index = -1;
int min = MAX;
for (i = 1; i <= G.vexnum; ++i)
{
if (min > closedge[i].lowcost && closedge[i].lowcost != 0)
{
min = closedge[i].lowcost;
index = i;
}
}
return index;
}
最小生成树Prim算法
void MiniSpanTree_P(MGraph G, VertexType u)//u为初始选择的顶点
{
int k; //k是顶点u的编号
k = LocateVex(G, u);//确定点u在G中的位置
int i, j;
for (j = 1; j <= G.vexnum; ++j)//遍历所有顶点 //辅助数组初始化
{
if (j != k)//不是开始顶点
{
closedge[j].adjvex = u;
closedge[j].lowcost = G.arcs[k][j].adj; //如果没有边就是MAX
}
}
closedge[k].lowcost = 0;//初始化开始顶点
for (i = 1; i < G.vexnum; ++i)//向生成树上添加顶点 //循环n-1次
{
k = Minimum(G);//求出加入生成树的下一个顶点k
cout << closedge[k].adjvex << "-" << G.vexs[k] << endl;//输出生成树上的这一条边
closedge[k].lowcost = 0;//第k顶点并入U集
for (j = 1; j <= G.vexnum; ++j)//用k更新其他顶点 //修改其他顶点的最小边
{
if (G.arcs[k][j].adj < closedge[j].lowcost)
{
closedge[j].adjvex = G.vexs[k];
closedge[j].lowcost = G.arcs[k][j].adj;
}
}
}
}
③算法分析
1、时间复杂性主要体现在两层循环上,复杂性是 O ( n 2 ) O(n_2) O(n2)
2、空间复杂性主要体现在两个辅助数组,复杂性是 O ( n ) O(n) O(n)
(2)kruskal算法
①思想
②实现(邻接矩阵)
//算法6.9 克鲁斯卡尔算法
#include <iostream>
using namespace std;
typedef char VerTexType; //假设顶点的数据类型为字符型
typedef int ArcType;
#define MVNum 100 //最大顶点数
#define MaxInt 32767 //表示极大值,即∞
//----------------图的邻接矩阵---------------------
typedef struct{
VerTexType vexs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum,arcnum; //图的当前点数和边数
}AMGraph;
//辅助数组Edges的定义
struct{
VerTexType Head; //边的始点
VerTexType Tail; //边的终点
ArcType lowcost; //边上的权值
}Edge[(MVNum * (MVNum - 1)) / 2];
int Vexset[MVNum]; //辅助数组Vexset的定义
int LocateVex(AMGraph G , VerTexType v){
//确定点v在G中的位置
for(int i = 0; i < G.vexnum; ++i)
if(G.vexs[i] == v)
return i;
return -1;
}//LocateVex
void CreateUDN(AMGraph &G){
//采用邻接矩阵表示法,创建无向网G
int i , j , k;
cout <<"请输入总顶点数,总边数,以空格隔开:";
cin >> G.vexnum >> G.arcnum; //输入总顶点数,总边数
cout << endl;
cout << "输入点的名称,如a" << endl;
for(i = 0; i < G.vexnum; ++i){
cout << "请输入第" << (i+1) << "个点的名称:";
cin >> G.vexs[i]; //依次输入点的信息
}
cout << endl;
for(i = 0; i < G.vexnum; ++i) //初始化邻接矩阵,边的权值均置为极大值MaxInt
for(j = 0; j < G.vexnum; ++j)
G.arcs[i][j] = MaxInt;
cout << "输入边依附的顶点及权值,如a b 6" << endl;
for(k = 0; k < G.arcnum;++k){ //构造邻接矩阵
VerTexType v1 , v2;
ArcType w;
cout << "请输入第" << (k + 1) << "条边依附的顶点及权值:";
cin >> v1 >> v2 >> w; //输入一条边依附的顶点及权值
i = LocateVex(G, v1); j = LocateVex(G, v2); //确定v1和v2在G中的位置,即顶点数组的下标
G.arcs[i][j] = w; //边<v1, v2>的权值置为w
G.arcs[j][i] = G.arcs[i][j]; //置<v1, v2>的对称边<v2, v1>的权值为w
Edge[k].lowcost = w;
Edge[k].Head = v1;
Edge[k].Tail = v2;
}//for
}//CreateUDN
//----------冒泡排序-------------------
void Sort(AMGraph G){
int m = G.arcnum - 2;
int flag = 1;
while((m > 0) && flag == 1){
flag = 0;
for(int j = 0 ; j <= m ; j++){
if(Edge[j].lowcost > Edge[j+ 1].lowcost){
flag = 1;
VerTexType temp_Head = Edge[j].Head;
Edge[j].Head = Edge[j+ 1].Head;
Edge[j + 1].Head = temp_Head;
VerTexType temp_Tail = Edge[j].Tail;
Edge[j].Tail = Edge[j+ 1].Tail;
Edge[j + 1].Tail = temp_Tail;
ArcType temp_lowcost = Edge[j].lowcost;
Edge[j].lowcost = Edge[j+ 1].lowcost;
Edge[j + 1].lowcost = temp_lowcost;
}//if
}//for
--m;
}//while
}//Sort
void MiniSpanTree_Kruskal(AMGraph G){
//无向网G以邻接矩阵形式存储,构造G的最小生成树T,输出T的各条边
int i , j , v1 , v2 , vs1 , vs2;
Sort(G); //将数组Edge中的元素按权值从小到大排序
for(i = 0; i < G.vexnum; ++i) //辅助数组,表示各顶点自成一个连通分量
Vexset[i] = i;
for(i = 0; i < G.arcnum; ++i){
//依次查看排好序的数组Edge中的边是否在同一连通分量上
v1 =LocateVex(G, Edge[i].Head); //v1为边的始点Head的下标
v2 =LocateVex(G, Edge[i].Tail); //v2为边的终点Tail的下标
vs1 = Vexset[v1]; //获取边Edge[i]的始点所在的连通分量vs1
vs2 = Vexset[v2]; //获取边Edge[i]的终点所在的连通分量vs2
if(vs1 != vs2){ //边的两个顶点分属不同的连通分量
cout << Edge[i].Head << "-->" << Edge[i].Tail << endl; //输出此边
for(j = 0; j < G.vexnum; ++j) //合并vs1和vs2两个分量,即两个集合统一编号
if(Vexset[j] == vs2) Vexset[j] = vs1; //集合编号为vs2的都改为vs1
}//if
}//for
}//MiniSpanTree_Kruskal
void main(){
cout << "************算法6.9 克鲁斯卡尔算法**************" << endl << endl;
AMGraph G;
CreateUDN(G);
cout <<endl;
cout << "*****无向网G创建完成!*****" << endl;
cout <<endl;
MiniSpanTree_Kruskal(G);
}///main
(3)比较
五、有向无环图
(一)拓扑排序
1、特点
2、思想
3、实现(邻接表)
(1)邻接表的数据结构(头文件,预定义,元素数据类型定义)
#include <stack>
#define MAX_VERTEX_NUM 20 //最大顶点个数
typedef int InfoType; //其他额外信息数据类型
typedef int vertextype;//顶点信息数据类型
//边表
typedef struct ArcNode
{
int adjvex; //该弧指向的顶点的位置(弧的另一个顶点)
struct ArcNode* nextarc;//指向下一条弧的指针
InfoType* info; //其他额外信息
}ArcNode;
//顶点表
typedef struct vnode
{
vertextype data; //顶点信息
ArcNode* firstarc; //顶点指向边表
}vnode, adjlist[MAX_VERTEX_NUM];
//图
typedef struct
{
adjlist vertices; //存顶点表(边的信息实际上是在顶点表上的)
int vexnum, arcnum; //顶点数,弧数
}ALGraph;
(2)求各点入度算法
void FindInDegree(ALGraph G, int ingedree[]/*顶点入度数组*/)
{
int i;
ArcNode* p;
for (i = 1; i <= G.vexnum; ++i)
{
p = G.vertices[i].firstarc; //指向第一条出边
while (p)//p不空
{
++ingedree[p->adjvex];//该顶点入度+1
p = p->nextarc;//p指向下一个出边
}
}
}
(2)拓扑排序主算法
bool TopologicalSort(ALGraph G)
{
int count = 0;//对输出顶点计数
int k, i;
ArcNode* p;
int indegree[MAX_VERTEX_NUM];//定义入度数组
for (i = 1; i <= G.vexnum; ++i)//初始化入度数组
indegree[i] = 0;
FindInDegree(G, indegree);//对各个顶点求入度
stack<int>S;//定义栈S——存入度为0的顶点
for (i = 1; i <= G.vexnum; ++i)//遍历入度数组,找入读为0的顶点入栈
{
if (!indegree[i])
S.push(i);//对入度为0的顶点入栈
}
while (!S.empty())
{
i = S.top();
S.pop();
cout << G.vertices[i].data << endl; //栈顶元素输出
++count;//拓扑顶点计数+1
for (p = G.vertices[i].firstarc; p != NULL; p = p->nextarc)//遍历顶点i的边表(出边表)
{
k = p->adjvex;
if (!(--indegree[k]/*k的入度-1*/))//若减完为0则入栈
S.push(k);
}
}
if (count < G.vexnum)//判断是否遍历完全or是否有环
return false;
else
return true;
}
4、算法分析
(二)关键路径
1、关键路径的有关概念
(1)AOE网
(2)关键路径
在AOE 网中,有些活动可以同时进行,完成一个工程所需的最短时间是从源点到汇点的最长路径长度。
长度最长的路径称为关键路径。
(3)关键活动
关键路径上的活动都是关键活动。
(4) 事件的最早发生时间与最迟发生时间
(5)活动的最早发生时间与最迟发生时间
活动的最早发生时间与最迟发生时间的性质
2、求关键活动算法要点
3、求事件的最早发时间和最晚发生时间步骤
(1)前进阶段——最早发生时间
(2)回退阶段——最迟发生时间
4、实现
(1)邻接表数据结构(头文件,预定义,元素数据类型)
#include <stack>
#define MAX_VERTEX_NUM 20
typedef int VertexType;
typedef float InfoType;
//边表
typedef struct ArcNode
{
int adjvex; //该弧所指向的顶点的位置
struct ArcNode* nextarc;//指向下一条弧的指针
InfoType* info; //存权值
}ArcNode;
//顶点表
typedef struct vnode
{
VertexType data; //顶点信息
ArcNode* firstarc; //指向第一条哦依附于该顶点的弧
}vnode, adjlist[MAX_VERTEX_NUM];
//图
typedef struct
{
adjlist vertices;
int vexnum, arcnum;
}ALGraph;
(2)记录事件的最早发生和事件的最晚发生时间数组类型
//记录事件的最早发生和事件的最晚发生时间
float ve[MAX_VERTEX_NUM], vl[MAX_VERTEX_NUM];
(3)求各个顶点入度算法
//求各个顶点入度算法
void FindInDegree(ALGraph G, int ingedree[]/*顶点入度数组*/)
{
int i;
ArcNode* p;
for (i = 1; i <= G.vexnum; ++i)
{
p = G.vertices[i].firstarc; //指向第一条出边
while (p)//p不空
{
++ingedree[p->adjvex];//该顶点入度+1
p = p->nextarc;//p指向下一个出边
}
}
}
(4)求事件最早发生时间(拓扑排序)
//求事件最早发生时间(拓扑排序)
bool TopologicalOrder(ALGraph G, stack<int>& T)
{
// 有向网G采用邻接表存储结构,
// 求各顶点事件的最早发生时间ve(全局变量)。
// T为拓扑序列顶点栈,S为零入度顶点栈。
// 若G无回路,则用栈T返回G的一个拓扑序列,
// 且函数值为OK,否则为ERROR。
int count = 0;//对输出顶点计数
int k, j;
ArcNode* p;
int indegree[MAX_VERTEX_NUM]; //定义入度数组
for (j = 1; j <= G.vexnum; ++j) //初始化入度数组
indegree[j] = 0;
for (j = 1; j <= G.vexnum; ++j) //初始化最早发生时间数组
ve[j] = 0;
FindInDegree(G, indegree);//对各个顶点求入度
stack<int>S;//定义栈S——存入度为0的顶点
for (j = 1; j <= G.vexnum; ++j) //遍历入度数组,找入读为0的顶点入栈
{
if (!indegree[j])
S.push(j);//对入度为0的顶点入栈
}
while(!S.empty())
{
j = S.top();
S.pop(); //栈顶元素出栈
T.push(j); //顶点拓扑序列存入栈T里面(不同之处)
++count; //拓扑顶点计数+1
for (p = G.vertices[j].firstarc; p != NULL; p = p->nextarc)//遍历顶点i的边表(出边表)
{
k = p->adjvex;
if (!(--indegree[k]/*k的入度-1*/))//若减完为0则入栈
S.push(k);
if (ve[j] + *(p->info) > ve[k]) //求最早发生时间(不同之处)
ve[k] = ve[j] + *(p->info); //更新为最大值(不同之处)
}
}
if (count < G.vexnum)//判断是否遍历完全or是否有环
return false;
else
return true;
}
(5)求事件最迟发生时间并输出关键路径
//求事件最迟发生时间并输出关键路径
bool CriticalPath(ALGraph G)
{
stack<int>T;
int j, k;
int i;
if (!TopologicalOrder(G, T))//判断是否能正向拓扑,不能的话反向也不行
return false;
for (i = 1; i <= G.vexnum; ++i)
vl[i] = ve[G.vexnum];//初始化定点时间的最迟发生时间(初始化赋值为整个工程时间)
ArcNode* p;
while (!T.empty())//按拓扑逆序求各个顶点的vl值
{
for (j = T.top(), T.pop(), p = G.vertices[j].firstarc; p != NULL; p = p->nextarc)//遍历Vj的出边表
{
k = p->adjvex;
if (vl[k] - *(p->info) < vl[j])
vl[j] = vl[k] - *(p->info);
}
}
int ee, el;//ee:某活动最早发生时间;el:某活动最迟发生时间
for (j = 1; j <= G.vexnum; ++j)//求ee,el和关键活动
{
for (p = G.vertices[j].firstarc; p != NULL; p = p->nextarc)
{
k = p->adjvex;//边的另一个顶点
ee = ve[j]; //活动最早发生时间
el = vl[k] - *(p->info);//活动最迟发生时间
char tag;
tag = (ee == el) ? '*' : ' ';//是关键活动标注*,否则标注空格
cout << j << "-" << k << "(" << *(p->info) << ")" << ee << " " << el << " " << tag << endl;//输出关键活动
}
}
return true;
}
5、算法分析
六、最短路径
(一)单源最短路径(Dijkstra算法)
1、思想
2、算法要点
3、算法演示
4、实现(邻接矩阵)
(1)邻接矩阵图的定义
#define MAX_VERTEX_NUM 20
#define INFINITY 1000
typedef int VertexType;
//图的定义
typedef struct
{
VertexType vexs[MAX_VERTEX_NUM];
float arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
int vexnum, arcnum;
}MGraph;
(2)三个遍历数组
int D[MAX_VERTEX_NUdM], P[MAX_VERTEX_NUM];各顶点到源点的距离值,初始化最短路径(前一个顶点)
bool final[MAX_VERTEX_NUM]; //顶点是否遍历到了
(3)单源最短路径主算法
void DisplayPath(MGraph G)
{
//输出v0到各个顶点的最小距离值(距离数组的数值)和路径
int i;
int pre;
for (i = 1; i <= G.vexnum; ++i)
{
cout << "\n" << "(" << D[i] << ")\t" << i;//先输出最后一个顶点i(从后往前输出)
pre = P[i];//i的前一个顶点
while (pre != -1)//还未到开始顶点(气质顶点的前一个顶点为-1)
{
cout << "←" << pre;//输出i前一个顶点
pre = P[pre];//再求前一个顶点的前一个顶点
}
}
}
(4)显示最短路
void DisplayPath(MGraph G)
{
//输出v0到各个顶点的最小距离值(距离数组的数值)和路径
int i;
int pre;
for (i = 1; i <= G.vexnum; ++i)
{
cout << "\n" << "(" << D[i] << ")\t" << i;//先输出最后一个顶点i(从后往前输出)
pre = P[i];//i的前一个顶点
while (pre != -1)//还未到开始顶点(气质顶点的前一个顶点为-1)
{
cout << "←" << pre;//输出i前一个顶点
pre = P[pre];//再求前一个顶点的前一个顶点
}
}
}
5、算法分析
(二)各顶点之间的最短路径(Floyd算法)
1、思想
2、算法要点
3、实现
(1)邻接矩阵图的定义
#define MAX_VERTEX_NUM 20
#define INFINITY 1000
typedef int VertexType;
//图的定义
typedef struct
{
VertexType vexs[MAX_VERTEX_NUM];
float arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
int vexnum, arcnum;
}MGraph;
(2)路径数组和距离矩阵
bool P[MAX_VERTEX_NUM][MAX_VERTEX_NUM][MAX_VERTEX_NUM];//路径数组 //从v到w是否经过了u
int D[MAX_VERTEX_NUM][MAX_VERTEX_NUM];//距离矩阵 //记录顶点vi和vj之间的最短路径长度
(3)各顶点之间的最短路径主程序
void ShortestPath_Floyed(MGraph G)
{
int v, w, u, i;
for (v = 1; v <= G.vexnum; ++v)//初始化
{
for (w = 1; w <= G.vexnum; ++w)
{
D[v][w] = G.arcs[v][w];//初始时,距离矩阵=邻接矩阵
for (u = 1; u <= G.vexnum; ++u)
{
P[v][w][u] = false;
if (D[v][w] < INFINITY)//若有边
P[v][w][v] = P[v][w][w] = true;
}
}
}
//三层循环
for (u = 1; u <= G.vexnum; ++u)//待插入的顶点(经过的顶点)
{
for (v = 1; v <= G.vexnum; ++v)
{
for (w = 1; w <= G.vexnum; ++w)
{
if (D[v][u] + D[u][v] < D[v][w])//从v经u到w的一条条路径更短
{
D[v][w] = D[v][u] + D[u][v];
for (i = 1; i <= G.vexnum; ++i)//判断是否经过其他顶点
if (P[v][u][i]/*前面经过i*/ || P[u][w][i]/*后面经过i*/)
//前面修改了v,u的路径,会影响后面的路径
P[v][w][i] = true;
}
}
}
}
}
4、算法分析
第8章 查找
一、静态查找表
(一)顺序查找(顺序表的查找)
1、顺序查找的思想
2、顺序表存储结构
(1)数据元素类型定义
typedef int KeyType;
typedef struct
{
KeyType key; //关键字
//··· //其他字段
}ElemType;
(2)顺序查找表存储结构
typedef struct
{
ElemType* elem; //一维动态数组
//数据元素存储空间基址,坚表示按照实际长度分配,0号单元指置空(最大长度+1)
int length; //查找表的长度
}SSTable;
3、代码实现
int Search_Seq(SSTable ST, KeyType key)
{// 在顺序表ST中顺序查找其关键字等于key的数据元素。若找到,则函数值为该元素在表中的位置,否则为0
ST.elem[0].key = key;//在0号位置设置监视哨(哨兵)
int i;
for (i = ST.length; ST.elem[i].key != key; --i);//从后往前找
return i; //找不到时,i为0
}
4、算法分析
(二)折半查找(有序表的查找)
1、折半查找的要求
2、折半查找的思想
3、有序表存储结构
(1)数据元素类型定义
typedef int KeyType;
typedef struct
{
KeyType key; //关键字
//··· //其他字段
}ElemType;
(2)有序查找表存储结构
typedef struct
{
ElemType* elem; //一维动态数组
//数据元素存储空间基址,坚表示按照实际长度分配,0号单元指置空(最大长度+1)
int length; //查找表的长度
}SSTable;
4、代码实现
//折半查找
int Search_Bin(SSTable ST, KeyType key)
{
int low = 1, high = ST.length; //置区间初值
int mid;
while (low <= high)
{
mid = (low + high) / 2;
if (key == ST.elem[mid].key)//EQ(key,ST.elem[mid].key)//相等
return mid;
else if (key < ST.elem[mid].key)//LT(key,ST.elem[mid].key)//小于
high = mid - 1;
else//大于
low = mid + 1;
}
return 0;//顺序表中不存在待查元素
}
5、算法分析
(三)索引顺序表的查找
1、索引查找表的存储要求
(1)线性表的要求
查找表分成 n n n块,当 i > j i>j i>j时,第 i i i块中的最小元素的关键字大于第j块中的最大元素的关键字
(2)索引表的要求
(1) 索引表是顺序存储
(2) 索引表里存储了各个块最大值和开始地址
2、索引顺序表的查找思想
3、索引顺序表的存储结构
(1)顺序表
①数据元素类型定义
typedef int KeyType;
typedef struct
{
KeyType key; //关键字
//··· //其他字段
}ElemType;
②有序查找表存储结构
typedef struct
{
ElemType* elem; //一维动态数组
//数据元素存储空间基址,坚表示按照实际长度分配,0号单元指置空(最大长度+1)
int length; //查找表的长度
}SSTable;
(2)索引表
①块的存储结构
typedef struct
{
KeyType key; //本块最大值
int addr; //本块开始地址
}IndexType;
②索引表的存储结构
#define MAXBLOCK 10
typedef struct
{
IndexType index[MAXBLOCK]; //一维结构体数组
int block;
}INTable;
4、代码实现
//索引表的建立
int Search(SSTable ST, INTable IX, KeyType key)
{
int i = 0, s, e;
//s记录在确定的块中对应查找表中的开始位置
//e记录在确定的块中对应查找表中的结束位置
//在索引表中查找,确定s和e的值
while ((key > IX.index[i].key)/*大→往后找*/ && (i < IX.block)/*i要小于索引表项数*/)//找块
++i;
if (i < IX.block)//若i未超过索引表长度(合法)→找到了块
{
s = IX.index[i].addr;//取开始位置
if (i == IX.block - 1)//若为最后一块:表尾
e = ST.length;
else//不是最后一块:下一块开始-1
e = IX.index[i + 1].addr - 1;
//根据s和e的值在线性表中查找
while (key != ST.elem[s].key && (s <= e))
s = s + 1;
if (s <= e)//若找到
return s;
}
return -1; //没找到
}
5、算法分析
二、动态查找表
(一)二叉排序树
1、定义
2、存储结构
(1)数据元素类型定义
typedef int KeyType;
typedef struct
{
KeyType key; //关键字
//··· //其他字段
}TElemType;
(2)二叉排序树结点的存储结构
用二叉链表存储二叉排序树
//二叉排序树的存储结构
typedef struct BiTNode
{
TElemType data;
struct BiTNode* lchild, * rchild;//左右孩子指针
}BiTNode, * BiTree;
3、结点查找
(1)思想
小于往左走
大于往右走
(2)代码实现
BiTree SearchBST(BiTree T, KeyType k)//返回结点指针
{
BiTree p = T;
while(p != NULL)
{
if (p->data.key == k)
return p;
if (k < p->data.key)
p = p->lchild;
else
p = p->rchild;
}
}
(3)算法分析
4、结点插入
(1)思想
(2)代码实现
①二叉排序树的查找算法(准备)
bool SearchBST(BiTree T, KeyType key, BiTree f/*T的父结点*/, BiTree& p/*关键字查找到相同的结点或者未查找到结点路径的最后一个结点*/)
{
if (!T)
//树空
{
p = f;
return false;//树为空时,p=NULL,返回值为假
}
else if (key == T->data.key)//EQ(key,T->data.key)
//树不空且相等
{
p = T;
return true;//找到时,p=找到结点,返回真值
}
else if (key < T->data.key)//LT(key,T->data.key)
//树不空且key比根结点小
return SearchBST(T->lchild, key, T, p);//返回左子树
else
//树不空且key比根结点大
return SearchBST(T->rchild, key, T, p);//返回右子树
}
②二叉排序树的插入算法
bool InsertBST(BiTree& T, TElemType e)
{
BiTNode* p, * s;
if (!SearchBST(T, e.key, NULL, p))//查找不成功//查找为假→未找到→可插入
{
//初始化新结点
s = (BiTNode*)malloc(sizeof(BiTNode));
s->data = e;
s->lchild = s->rchild = NULL;
if (!p)//看树是否为空
T = s;
//否则插入到p的左子树
else if (e.key < p->data.key)//LT(e.key, p->data.key))
//树不空且key比根结点小
p->lchild = s;//插入s为左孩子
else
//树不空且key比根结点大
p->rchild = s;//插入s为右孩子
return true;
}
else
return false;//树中已有关键字相同的结点,不再插入
}
(3)算法分析
5、结点删除
(1)思想
定义:在二叉查找树上的删除一个结点,要求删除结点后,仍保持二叉排序树的结构特点不变。
(2)代码实现
①删除结点算法
bool Delete(BiTree& p)
{
BiTNode* q, * s;
if (!p->rchild)//右子树为空(左右均空也进入这个)
{
q = p;
p = p->lchild;//左子树可以是NULL
free(q);
}
else if (!p->lchild)//左子树为空
{
q = p;
p = p->rchild;//右子树可以是NULL
free(q);
}
else//都不空
{
//初始化p和s的位置
q = p;
s = p->lchild;
while (s->rchild)//找左子树最右结点
{
q = s;//q始终是s的父结点
s = s->rchild;//最后s指向左子树最右结点
}
p->data = s->data;//用s来替换p
if (q != p)//一般情况→正常改
q->rchild = s->lchild;
else//p和q是一个点时,p(或q)只有左结点空着且左侧均是小于
q->lchild = s->lchild;
free(s);
}
return true;
}
②删主程序
bool DeleteBST(BiTree& T, KeyType key)
{
if (!T)
return false;
else
{
if (key == T->data.key)//EQ(key, T->data.key)
//查找是否相等
return Delete(T);
else if (key < T->data.key)//LT(key, T->data.key)
//小于
return DeleteBST(T->lchild, key);
else
//大于
return DeleteBST(T->rchild, key);
}
}
(3)算法分析
(二)平衡二叉树
1、定义
(1)平衡二叉树
(2)平衡因子
2、平衡二叉树的构造
(1)演示
(2)思想
(3)旋转方法
①LL(左左)型——整体右旋
②RR(右右)型——整体左旋
③LR(左右)型——先左子树左旋再整体右旋
④RL(右左)型——先右子树右旋在整体左旋
三、哈希表
(一)哈希表的定义及相关概念
(二)构造方法
1、直接定址法
2、质数求余法
3、平方取中法
4、折叠法
5、数字分析法
6、基数转换法
(三)处理冲突的方法
1、开放定址法
(1)定义
(2)演示(插入算法)
(3)查找算法
(4)删除算法
(5)处理冲突的方法
①线性探测再散列法
若
H
(
k
e
y
)
=
d
H(key)=d
H(key)=d的单元发生冲突,则按下述方法进行探查:
h
i
(
k
)
=
(
h
(
k
)
+
i
)
h_i(k)=(h(k)+i)%n
hi(k)=(h(k)+i),
n
n
n是散列表的长度,
1
≤
i
≤
n
−
1
1≤i≤n-1
1≤i≤n−1
②二次探测再散列法
若
H
(
k
e
y
)
=
d
H(key)=d
H(key)=d的单元发生冲突,则按下述方法进行探查:
h
i
(
k
)
=
(
h
(
k
)
+
d
i
)
h_i(k)=(h(k)+di)%n
hi(k)=(h(k)+di),
n
n
n是散列表的长度,
d
i
=
1
2
,
−
1
2
,
2
2
,
−
2
2
,
…
,
+
k
2
,
−
k
2
,
(
k
≤
n
/
2
)
di=1^2,-1^2,2^2,-2^2,…,+k^2,-k^2,(k≤n/2)
di=12,−12,22,−22,…,+k2,−k2,(k≤n/2)
③伪随机探测再散列
d i = 伪 随 机 序 列 di=伪随机序列 di=伪随机序列
④再哈希法(再散列法)
设置 R H i ( ) , i = 1 , 2 , … , k RH_i(), i=1,2,…,k RHi(),i=1,2,…,k,多个哈希函数,当一个哈希函数产生冲突时,顺序用下一个。
2、拉链法
(1)示例
(2)开放定址法与链地址法平均查找长度比较
(3)查找算法
(4)插入算法
(四)哈希表的查找及其分析
1、存储结构
(1)数据元素存储类型
typedef int KeyType;//注意先后顺序
typedef struct
{
KeyType key; //关键字
//··· //其他字段
}TElemType;
(2)哈希表存储类型
typedef struct
{
ElemType* elem; //数组
int count; //当前数据元素个数
int sizeindex; //当前容量
}HashTable;
2、代码实现
int Hash(HashTable H, int key)
{
return key % H.sizeindex;
}
int SearchHash(HashTable H, int key)
{//在哈希表HT中查找关键字为key的元素,若查找成功,返回哈希表的单元标号,否则返回-1
int H0 = Hash(H,key); //根据哈希函数Hash(key)计算哈希地址
if (H.elem[H0].key == NULL) //若单元H0为空,则所查元素不存在
return -1;
else if (H.elem[H0].key == key) //若单元H0中元素的关键字为key,则查找成功
return H0;
else
{
int Hi;
for (int i = 1; i < H.sizeindex; ++i)
{
Hi = (H0 + i) % H.sizeindex; //按照线性探测法计算下一个哈希地址Hi
if (H.elem[Hi].key == NULL) //若单元Hi为空,则所查元素不存在
return -1;
else if (H.elem[Hi].key == key) //若单元Hi中元素的关键字为key,则查找成功
return Hi;
}
return -1;
}
}
3、算法分析
4、散列法与其他方法的比较
第9章 内部排序
存储结构
(除了基数排序链队列其他都相同)
#define MAXSIZE 1000 //待排顺序表最大长度
typedef int KeyType; //关键字类型为整数类型
typedef int InfoType;
//记录类型
typedef struct
{
KeyType key; //关键字项
InfoType othterinfo; //其他数据项
}RcdType;
//顺序表类型
typedef struct
{
RcdType r[MAXSIZE + 1]; //r[0]闲置
int length; //顺序表长度
}SqList;
一、插入排序
(一)直接插入排序
1、演示
2、思想
3、代码实现
(1)存储结构
#define MAXSIZE 1000 //待排顺序表最大长度
typedef int KeyType; //关键字类型为整数类型
typedef int InfoType;
//记录类型
typedef struct
{
KeyType key; //关键字项
InfoType othterinfo; //其他数据项
}RcdType;
//顺序表类型
typedef struct
{
RcdType r[MAXSIZE + 1]; //r[0]闲置
int length; //顺序表长度
}SqList;
(2)算法
void InsertionSort(SqList& L)
{//对顺序表L作直接插入排序
int i, j;
for (i = 2; i <= L.length; ++i)//从2到n,共n-1趟
{
if (L.r[i].key < L.r[i - 1].key)//若比有序序列最后一个元素小则进入下面,否则该元素不动指向下一个元素
{
L.r[0] = L.r[i];//复制为监视哨
for (j = i - 1; L.r[0].key < L.r[j].key; --j)//有序序列从后往前比较
L.r[j + 1] = L.r[j];//比他大,记录后移
L.r[j + 1] = L.r[0];//插到正确的位置(不比他大,小于等于时)
}
}
}
4、算法分析
(二)折半插入排序
1、思想
2、演示
3、代码实现
(1)存储结构
#define MAXSIZE 1000 //待排顺序表最大长度
typedef int KeyType; //关键字类型为整数类型
typedef int InfoType;
//记录类型
typedef struct
{
KeyType key; //关键字项
InfoType othterinfo; //其他数据项
}RcdType;
//顺序表类型
typedef struct
{
RcdType r[MAXSIZE + 1]; //r[0]闲置
int length; //顺序表长度
}SqList;
(2)算法
void BiInsertionSort(SqList& L)
{
int i, j;
for (i = 2; i <= L.length; ++i)//从2到n,共n-1趟
{
if (L.r[i].key < L.r[i - 1].key)//待插入的与有序序列最后一个元素比较
L.r[0] = L.r[i];//将 L.r[i] 暂存到 L.r[0](监视哨)
//在 L.r[1..i-1]中折半查找插入位置
int low = 1, high = i - 1;
int m;
while (low <= high)
{
m = (low + high) / 2;//折半
if (L.r[0].key < L.r[m].key)
high = m - 1;
else
low = m + 1;
}
for (j = i - 1; j >= high + 1; --j)
L.r[j + 1] = L.r[j];//high后的元素右移
L.r[high + 1] = L.r[0];//在high后插入这个元素
}
}
4、算法分析
(三)希尔排序
1、思想
2、演示
3、代码实现
(1)存储结构
①顺序表
#define MAXSIZE 1000 //待排顺序表最大长度
typedef int KeyType; //关键字类型为整数类型
typedef int InfoType;
//记录类型
typedef struct
{
KeyType key; //关键字项
InfoType othterinfo; //其他数据项
}RcdType;
//顺序表类型
typedef struct
{
RcdType r[MAXSIZE + 1]; //r[0]闲置
int length; //顺序表长度
}SqList;
②增量数组
(2)算法
//数值型关键字的比较
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))
//字符型关键字的比较
#define EQ(a,b) (!strcmp((a),(b)))
#define LT(a,b) (strcmp((a),(b))<0)
#define LQ(a,b) (strcmp((a),(b)<=0)
①增量为dk时的希尔排序(一轮)
void ShellInsert(SqList& L, int dk)
{
int i, j;
for (i = dk + 1; i < L.length; ++i)//从dk+1到n的元素进行希尔排序
{
if (LT(L.r[i].key, L.r[i - dk].key))//该元素比该子序列前有序序列最后一个元素小
{
L.r[0] = L.r[i];//暂存到r[0]
for (j = i - dk; j > 0 && LT(L.r[0].key, L.r[j].key); j -= dk)//在该子序列中有序序列从后往前比大小
L.r[j + dk] = L.r[j];//记录后移,查找插入位置;若r[0]小,则该比较元素后移
L.r[j + dk] = L.r[0];//找到后插入
}
}
}
②算法主函数
void ShellSort(SqList& L, int dlta[]/*增量数组*/, int t/*增量个数*/)
{
int k;
for (k = 0; k < t; ++k)
ShellInsert(L, dlta[k]);
}
4、算法分析
二、交换排序
(一)冒泡排序
1、演示
2、思想
3、算法
(1)存储结构
#define MAXSIZE 1000 //待排顺序表最大长度
typedef int KeyType; //关键字类型为整数类型
typedef int InfoType;
//记录类型
typedef struct
{
KeyType key; //关键字项
InfoType othterinfo; //其他数据项
}RcdType;
//顺序表类型
typedef struct
{
RcdType r[MAXSIZE + 1]; //r[0]闲置
int length; //顺序表长度
}SqList;
(2)算法
void BubbleSort(SqList& L)
{
int i, j, noswap;
RcdType temp;
for (i = 1; i <= L.length; ++i)//n-1轮
{
noswap = true;//先设标志变量——不需要变换标志变量为真
for (j = 1; j <= L.length - i; ++j)//1到n-i冒泡排序
{
if (L.r[j].key > L.r[j + 1].key)
{
//交换
temp = L.r[j];
L.r[j] = L.r[j + 1];
L.r[j + 1] = temp;
noswap = false;//标志变量为假——变化了
}
}
}
}
4、算法分析
(二)快速排序
1、思想
2、示例
3、代码实现
(1)存储结构
#define MAXSIZE 1000 //待排顺序表最大长度
typedef int KeyType; //关键字类型为整数类型
typedef int InfoType;
//记录类型
typedef struct
{
KeyType key; //关键字项
InfoType othterinfo; //其他数据项
}RcdType;
//顺序表类型
typedef struct
{
RcdType r[MAXSIZE + 1]; //r[0]闲置
int length; //顺序表长度
}SqList;
(2)算法
①划分函数
int Partition(SqList& L, int low, int high)//从low到high无序序列来划分
{
KeyType pivotkey;//基准
L.r[0] = L.r[low];//基准暂存0号单元→因为不用交换了
pivotkey = L.r[low].key;//把一开始low指针指向的位置作为基准
while (low < high)
{
while (low < high && L.r[high].key >= pivotkey)//从后往前扫描
--high;
L.r[low] = L.r[high];
while (low < high && L.r[high].key <= pivotkey)//从前往后扫描
++low;
L.r[high] = L.r[low];
}
L.r[low] = L.r[0];//枢纽记录到位
return low;//返回枢纽位置
}
②算法主函数
void QSort(SqList& L, int low, int high)
{
int pivotloc;//基准
if (low < high)
{
pivotloc = Partition(L, low, high);//将L.r[low···high]一分为二
QSort(L, low, pivotloc - 1); // 对低子表递归排序,pivotloc是枢轴位置
QSort(L, pivotloc + 1, high);// 对高子表递归排序
}
}
4、算法分析
三、选择排序
(一)简单选择排序
1、思想
2、演示
3、代码实现
(1)存储结构
#define MAXSIZE 1000 //待排顺序表最大长度
typedef int KeyType; //关键字类型为整数类型
typedef int InfoType;
//记录类型
typedef struct
{
KeyType key; //关键字项
InfoType othterinfo; //其他数据项
}RcdType;
//顺序表类型
typedef struct
{
RcdType r[MAXSIZE + 1]; //r[0]闲置
int length; //顺序表长度
}SqList;
(2)算法
void SelectSort(SqList& L)
{
int i, j, low;
for (i = 1; i < L.length; ++i)//n-1次排序
{
low = i;
for (j = i + 1; j <= L.length; ++j)//从i到n来选择排序
{
if (L.r[j].key < L.r[low].key)
low = j;//记录最小的下标
if (i != low)//若i对应的不是最小值
{
//借助r[0]来进行交换
//r[i]与r[low]进行交换
L.r[0] = L.r[i];
L.r[i] = L.r[low];
L.r[low] = L.r[0];
}
}
}
}
4、算法分析
(二)堆排序
1、引入
2、堆的定义
(1)小根堆例子
(2)大根堆例子
3、演示
4、思想
5、代码实现(大根堆)
(1)存储结构
#define MAXSIZE 1000 //待排顺序表最大长度
typedef int KeyType; //关键字类型为整数类型
typedef int InfoType;
//记录类型
typedef struct
{
KeyType key; //关键字项
InfoType othterinfo; //其他数据项
}RcdType;
//顺序表类型
typedef struct
{
RcdType r[MAXSIZE + 1]; //r[0]闲置
int length; //顺序表长度
}SqList;
(2)算法
①筛选调整算法
void HeapAdjust(SqList& L, int s/*要调整结点的位置*/, int m/*最后一个有孩子结点的位置*/)
{
int j;
ElemType rc;
rc = L.r[s];//要调整的点暂存起来
for (j = 2 * s/*j先指向左孩子*/; j <= m/*保证有孩子结点*/; j *= 2)
{
if (j < m && L.r[j].key < L.r[j + 1].key)//判断有无右孩子and左右孩子谁更大
++j;
if (rc.key >= L.r[j].key)//比较待调整结点和孩子中比较大的那个
break;//满足大小关系则退出循环
L.r[s] = L.r[j];//不满足则交换
s = j;//看看上面换完后下面是否还符合
}
}
②堆排序算法
void HeapSort(SqList& L)
{
int i;
ElemType temp;
//①建堆
for (i = L.length / 2; i > 0; --i)
HeapAdjust(L, i, L.length);
//n-1遍循环
for (i = L.length; i > 1; --i)
{
//②交换
temp = L.r[i];
L.r[i] = L.r[1];
L.r[1] = temp;
//③调整为堆
HeapAdjust(L, 1, i - 1);//剩下的i-1个重新建堆
//只有1个元素需要调整→栈顶元素(只有他不符合)
}
}
6、算法分析
四、归并排序
(一)普通归并排序
1、归并的定义
2、演示
3、代码实现
(1)存储结构
#define MAXSIZE 1000 //待排顺序表最大长度
typedef int KeyType; //关键字类型为整数类型
typedef int InfoType;
//记录类型
typedef struct
{
KeyType key; //关键字项
InfoType othterinfo; //其他数据项
}RcdType;
(2)算法
//数值型关键字的比较
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))
//字符型关键字的比较
#define EQ(a,b) (!strcmp((a),(b)))
#define LT(a,b) (strcmp((a),(b))<0)
#define LQ(a,b) (strcmp((a),(b)<=0)
void Merge(RcdType SR[], RcdType TR[], int i, int m, int n)//具体含义见图片
{
int j, k;
for (j = m + 1, k = i; i <= m && j <= n; ++k)
{
if (LQ(SR[i].key, SR[j].key))//前后两个有序序列元素比较大小
TR[k] = SR[i++];
else
TR[k] = SR[j++];
}
if (i <= m)//多余的部分整体搬移过去//前面剩了
{
while (k <= n && i <= m)
TR[k++] = SR[i++];
}
if (j <= n)//多余的部分整体搬移过去//后面剩了
{
while (k <= n && j <= n)
TR[k++] = SR[j++];
}
}
(二)2-路归并排序
1、示例
2、思想
3、代码实现
(1)存储结构
#define MAXSIZE 1000 //待排顺序表最大长度
typedef int KeyType; //关键字类型为整数类型
typedef int InfoType;
//记录类型
typedef struct
{
KeyType key; //关键字项
InfoType othterinfo; //其他数据项
}RcdType;
(2)算法
①普通归并算法
//数值型关键字的比较
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))
//字符型关键字的比较
#define EQ(a,b) (!strcmp((a),(b)))
#define LT(a,b) (strcmp((a),(b))<0)
#define LQ(a,b) (strcmp((a),(b)<=0)
void Merge(RcdType SR[], RcdType TR[], int i, int m, int n)//具体含义见图片
{
int j, k;
for (j = m + 1, k = i; i <= m && j <= n; ++k)
{
if (LQ(SR[i].key, SR[j].key))//前后两个有序序列元素比较大小
TR[k] = SR[i++];
else
TR[k] = SR[j++];
}
if (i <= m)//多余的部分整体搬移过去//前面剩了
{
while (k <= n && i <= m)
TR[k++] = SR[i++];
}
if (j <= n)//多余的部分整体搬移过去//后面剩了
{
while (k <= n && j <= n)
TR[k++] = SR[j++];
}
}
②有序序列长度为 l l l时的归并(核心算法)
void Msort(RcdType A[], RcdType B[], int n/*n个元素*/, int l/*有序序列长度*/)
{
int i = 1, t;
while (i + 2 * l - 1 <= n)//2个2个一组
{
Merge(A, B, i, i + l - 1, i + 2 * l - 1);//2个2个合并
i = i + 2 * l;//到下一对
}
if (i + l - 1 < n)//此时i+2l-1不小于等于n(到了最后一对不完整的了)最后还能有2个不等长的有序序列
Merge(A, B, i, i + l - 1, n);
else//还剩下1个有序序列
{
for (t = l; t <= n; ++t)
B[t] = A[t];//依次复制到目标数组
}
}
③2-路归并排序算法
void MergeSort(RcdType A[]/*原来待排序的数据*/, int n/*n个元素*/)
{
int l = 1;//有序序列长度
RcdType* B;
B = (RcdType*)malloc(n * sizeof(RcdType));//RcdType B[n];
while (l < n)//有序序列长度小于n时)
{
Msort(A, B, n, l);//把亮亮相里长为1合成长为2的有序序列并存到B中
l = 2 * l;//更改有序序列长度
Msort(B, A, n, l);//再把B的长为2有序序列两两合并存到A中
l = 2 * l;//更改有序序列长度
}
}