数据结构简单总结(下)

霍夫曼树

霍夫曼树就是最优二叉树。

最优二叉树是带权路径最短的树。

路径带权长度就是看叶子结点的权重乘以到这个叶子节点路径长度的乘积

霍夫曼树的构造

使用贪心算法

就是在一堆叶子结点里面,先选出最小的两个,变成一个小二叉树

然后在这两个小二叉树的双亲写上权重,继续放在上述位置,进行递归。

然后可以形成霍夫曼编码

往左就是0,往右就是1;

然后到最后,倒过来,就会形成霍夫曼编码。

这种编码也是一种前缀编码,所谓前缀编码就是任何一个字符的编码都不是另一个字符编码的前缀。这样可以避免解码时候的歧义。

霍夫曼树的建立

建立霍夫曼树使用的是树的顺序存储结构。

树的结点这样定义

Struct  HufNode{

ElemType data;

Int parent,lchild,rchild;//存放左右孩子 双亲

};

构建的时候

HufNode HuffmanTree[256];

就是先把整个数组的各种东西都给清0;

然后其实这种两两一次,最终会循环加上之前的n,会占用2n-1个地方

又因为教程上第0号位置不用

所以循环从i=n+1开始,一直到<=2n-1;

void CreateHuffmanTree(HuffmanTree& HT, int n) {

if (n <= 1) return;

auto m = 2 * n - 1;//一共这些元素

HT = new HTnode[m + 1];//申请多一个元素

for (int i = 1; i <= m; i++) {

HT[i].lchild = 0;

HT[i].rchild = 0;

HT[i].parent = 0;

}

for (int i = 1; i <= n; i++) std::cin >> HT[i].data.weight>>HT[i].data.ch;//手动输入权重

for (int i = n+1; i <=m; i++) {

int s1, s2;

Select(HT, i - 1, s1, s2);//函数实现 自写 从1-i里面选择

HT[s1].parent = i; HT[s2].parent = i;

HT[i].rchild = s1;

HT[i].lchild = s2;

HT[i].data.weight = HT[s1].data.weight + HT[s2].data.weight;

}

}

这个就是算法的具体实现。这样就可以构建一个huffman树了

然后是输出huffman编码

可以利用栈的先进后出的特性,来解出huffman编码

从给定的一个地方开始遍历。

用个temp变量,保存现在正在遍历的元素

While循环 循环的条件是双亲是!=NULL(0),

然后进行判断,如果是双亲的左孩子,就push0,右孩子就1;

然后改变temp=temp&HT[temp->parent];//变成双亲

void HoffmanTreeReverse(HuffmanTree HT,int n) {

for (int i = 1; i <= n; i++) {

Stack s;

auto temp = HT+i;

while (temp->parent != NULL) {

if (HT+HT[temp->parent].lchild ==temp ) {

//左孩子是

s.push(1);

}

if (HT + HT[temp->parent].rchild == temp) {

s.push(0);

}

temp = &HT[temp->parent];//迭代更新

}

std::cout << HT[i].data.ch << "'s Huffman Code:";

s.show();

}

}

最后是huffman树的解码。这个就给一组树,是0就往左,1就往右,一直等到遍历到没用左右孩子即可。

第六章 图

所谓图,就是一种多对多的数据结构

图也有非常非常多的概念

比如假如线是没用顺序的,那就是无向图,

假如有方向,那就是有向图。

假如线或者弧都是有权重的,那么就属于网。

网也分为无向网和有向网。他们四个构建的方法有些许不同,但是大部分都是相同的。

图比较重要的是遍历 已经他的两种重要的储存形式

分别是邻接矩阵和邻接表进行存储。

首先

  1. 临界矩阵的数据结构

假如是无向图,没有权重,那么不是链接的就用0来代替

就是用个矩阵(二维数组)表示。一行就是来表示和第几个点连接起来了。

无向的是对称的,但是有向的不一定。

所以数据结构一般是

Struct AMGraph{

Char VexName[100];

AcrType arcs[100][100];//表示矩阵

Int vexnum,arcnum;//表示顶点和边的个数

}

而创建无向图的算法是

void CreateUDN(AMGraph& G) {

std::cin >> G.arcnum >> G.vexnum;//输入总点数和总变数

for (int i = 0; i < G.vexnum; i++) {

std::cin >> G.vexs[i];

}

for (int i = 0; i < 100; i++) {

for (int j = 0; j < 100; j++) {

G.arcs[i][j] = INFINITE;

}

}

for (int i = 0; i < G.arcnum; i++) {

int v1 = 0, v2 = 0,iWight=0;//注意 顶点的类型可不一定是int

std::cin >> v1 >> v2 >> iWight;//输入这个边依附的两个顶点 和权重

int j = LocateVex(G, v1);//找顶点 假如直接输入0....n 那就不用找了

int k = LocateVex(G, v2);

G.arcs[i][j] = iWight;

G.arcs[j][i] = iWight;//无向网对称

//有向网就可以删除上面的就行

}

}

这个是创建无向网的算法。LocateVex这个函数是根据顶点名字来找到顶点下标

其他三种图可以改一下上面的东西就可以了。

  1. 邻接表的数据结构

邻接表的数据结构

他是由一个表头,也就是顶点。指向与他相关联的其他顶点。

假如是无向的,那么就是指向所有周围的顶点。

假如是有向的,那么就把所有出度链接起来。

邻接表的数据结构比较复杂。他首先要有表头,然后这个表头的指针域是指向下一个结点的

结点也会递归指向结点。

最后表头这个类型变成数组,存储着,每个顶点都在这个数组里面。

头结点的结构:

typedef struct VNode {//头结点

VerTexType data;//定点信息

ArcNode* fisrstarc;//指向第一条依附顶点的边的指针

}VNode,AdjList[100];//还定义了数组

注意 AdjList就是这样一个数组

表结点的数据结构:

typedef struct ArcNode {

int adjvex;//下表

struct ArcNode* nextarc;//下一条

Otherinfo info;//其他信息

};

最终整个数据结构就是

typedef struct {

AdjList vertices;//邻接表整个

int vexnum, arcnum;//图的顶点和边数

}ALGraph;

邻接表的构造就是

void CreateUDG(ALGraph& G) {

std::cout << "请分别输入边和顶点数";

std::cin >> G.arcnum >> G.vexnum;

for (int i = 0; i < G.vexnum; i++) {

std::cout << "请输入头的名字:";

std::cin >> G.vertices[i].data.name;

G.vertices[i].data.index = i;//在表中的第几个位置

G.vertices[i].fisrstarc = nullptr;

}

for (int k = 0; k < G.arcnum; k++) {

std::cout << "请输入相关联的两个顶点";

VexType v1, v2;

std::cin >> v1, v2;

int i=LocateVex(G, v1);

int j=LocateVex(G, v2);//获取索引

auto p1 = new ArcNode;

p1->adjvex = j;//设置索引

p1->nextarc = G.vertices[i].fisrstarc;//头插法

G.vertices[i].fisrstarc = p1;//头插法

auto p2 = new ArcNode;

p2->adjvex = i;//设置索引

p2->nextarc = G.vertices[j].fisrstarc;//头插法

G.vertices[j].fisrstarc = p1;//头插法

//无向表对称

}

}

具体来说也是比较简单,和邻接矩阵比较相似,区别就是链表的形式构造。

插入的话运用头插法

下面就是比较重要的两个图的遍历,分别是DFS和BFS

DFS就是深度优先,一条道走到黑。用递归思想

而BFS是广度优先,类似于二叉树的层次遍历,加个队列。

这两个都用到了一个叫做visited的数组,来判断是不是已经遍历过了

DFS就是

void AMGraph::DFS(char name,bool* visited) {

//深度优先查找

//参数是从哪个结点开始 方便迭代

int index = LoateVex(*this, name);//获取下表

visited[index] = true;//设置被访问过了

std::cout << name;

for (int j = 0; j < this->iNumvexs; j++) {

//这个时候访问的0-3 实际上是在看visited数组

if (visited[j] != true && this->acrs[index][j] != 0) {//说明这个时候有线 并且这个点没被访问

DFS(this->Vexs[j], visited);

}

}

}

这就是DFS的实现,运用到了递归

而BFS的实现则用到了队列,类似于层次遍历

void ALGraph::BFS() {

Queue Q;

std::cout << "请输入开始顶点的名字:";

char name;

std::cin >> name;

Q.EnQueue(this->AdList[LocateVex(*this, name)]);//从这个索引开始

visited[LocateVex(*this, name)] = true;//从输入的那个顶点开始

while (!Q.empty()) {

//队列不空

auto e = Q.DeQueue();//获取出队列的那个VexNode;

std::cout << e.name<<"被访问";

//

for (auto i = e.first; i; i=i->next) {

//for循环链表 一直到null链表

//其实是来找索引的

if (!visited[i->index]) {

Q.EnQueue(this->AdList[i->index]);//判断索引没有被访问 进队列

visited[i->index] = true;

}

}

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值