C语言范例学习03-下
树与图
3.5 二叉树及其应用
PS:二叉树是最经典的树形结构,适合计算机处理,具有存储方便和操作灵活等特点,而且任何树都可以转换成二叉树。
实例101 二叉树的递归创建 实例102 二叉树的遍历
问题:编程实现递归创建二叉树,要求显示树的节点内容,深度及叶子节点数。
构造一棵二叉树,分别采用先序遍历、中序遍历和后序遍历遍历该二叉树。
逻辑:二叉树是一棵由一个根节点(root)和两棵互不相交的分别称作根的左子树和右子树所组成的非空树,左子树和右子树有同样都是一棵二叉树。
存储二叉树通常采用二叉链表法,即每个节点带两个指针和一个数据域(是不是突然觉得和链表很像呢),一个指针指向左子树,另一个指针指向右子树。
在遍历二叉树时若先左后右,则有三种遍历方法,分别为先序遍历、中序遍历和后序遍历,它们的遍历定义如下:
先序遍历:若二叉树非空,则先访问根结点,再按先序遍历左子树,最后按先序遍历右子树。
中序遍历:若二叉树非空,则先按中序遍历左子树,再访问根结点,最后按中序遍历右子树。
后序遍历:若二叉树非空,则先按后序遍历左子树,再按后序遍历右子树,最后访问根结点。
代码:
#include<stdio.h>
#include<stdlib.h> typedef struct node
//二叉链表的结构声明
{
struct node *lchild;
char data;
struct node *rchild;
}bitnode,*bitree;
//结构体的类型。 bitree CreatTree()
//自定义函数CreatTree(),用于构造二叉树。
{
char a;
bitree new;
scanf("%c",&a);
if(a=='#')
//二叉树的输入标识
return NULL;
else
{
new=(bitree)malloc(sizeof(bitnode));
//申请内存空间。
new->data=a;
new->lchild=CreatTree();
new->rchild=CreatTree();
}
return new;
} void print(bitree bt)
//创建函数print(),用于输出二叉树的节点内容。PS:输出方式为中序遍历。
{
if(bt!=NULL)
{
print(bt->lchild);
printf("%c",bt->data);
print(bt->rchild);
}
} int btreedepth(bitree bt)
//自定义函数btreedepth(),用于求解二叉树的深度。
{
int ldepth,rdepth;
if(bt==NULL)
return ;
else
{
ldepth=btreedepth(bt->lchild);
rdepth=btreedepth(bt->rchild);
return(ldepth>rdepth?ldepth+:rdepth+);
//返回语句中,采用了三目运算符。不知道的可以看该系列之前的篇章。
}
} int ncount(bitree bt)
//自定义函数ncount(),用于求二叉树中节点个数。
{
if(bt==NULL)
return ;
else
return(ncount(bt->lchild)+ncount(bt->rchild)+);
} int lcount(bitree bt)
//自定义函数lcount(),用于求二叉树中叶子节点个数。
{
if(bt==NULL)
return ;
else if(bt->lchild==NULL && bt->rchild==NULL)
return ;
else
return(lcount(bt->lchild)+lcount(bt->rchild));
} void preorderTraverse(bitree bt)
//自定义函数preorderTraverse(),用于先序遍历、输出二叉树。
{
if(bt!=NULL)
{
printf("%c",bt->data);
preorderTraverse(bt->lchild);
preorderTraverse(bt->rchild);
}
} void InorderTraverse(bitree bt)
//自定义函数InorderTraverse(),用于中序遍历、输出二叉树。
{
if(bt!=NULL)
{
InorderTraverse(bt->lchild);
printf("%c",bt->data);
InorderTraverse(bt->rchild);
}
} void postorderTraverse(bitree bt)
//自定义函数postorderTraverse(),用于后序遍历、输出二叉树。
{
if(bt!=NULL)
{
postorderTraverse(bt->lchild);
postorderTraverse(bt->rchild);
printf("%c",bt->data);
}
} void main()
{
bitree root;
//初始化数据root。
root=CreatTree();
//调用函数创建二叉链表。
printf("contents of binary tree:\n");
print(root);
//调用函数输出节点内容。
printf("\ndepth of binary tree:%d\n",btreedepth(root));
//调用函数输出树的深度。
printf("the number of the nodes:%d\n",ncount(root));
//调用函数输出树中节点个数。
printf("the number of the leaf nodes:%d\n",lcount(root));
//调用函数输出树中叶子节点个数。
printf("preorder traversal:\n");
preorderTraverse(root);
//调用函数先序遍历输出二叉树。
printf("\n");
printf("inorder traversal:\n");
InorderTraverse(root);
//调用函数中序遍历输出二叉树。
printf("\n");
printf("postorder traversal:\n");
postorderTraverse(root);
//调用函数后序遍历输出二叉树。
printf("\n");
}
运行结果:
反思:整个程序代码,并没有多少新鲜东西。重要的是通过程序代码,理解二叉树创建、遍历的概念。
二叉树方面还有线索二叉树、二叉排序树等,但都不会打出代码。因为代码上并没有新的内容,主要是对其概念的理解。
不过,其中的哈夫曼编码还是得说说的。
实例105 哈夫曼编码
问题:已知a,b,c,d,e,f各节点的权值分别为18、20、4、13、16、38,采用哈夫曼编码法对各节点进行编码。
逻辑:哈夫曼编码算法:森林中共有n棵二叉树,每棵二叉树中仅有一个孤立的节点,他们既是根又是叶子,将当前森林中的两棵根结点权值最小的二叉树合并称一棵新的二叉树,每合并一次,森林中就减少一棵树。森林中n棵树要进行n-1次合并,才能使森林中的二叉树的数目由n棵减少到一棵最终的哈夫曼树。每次合并时都会产生一个新的节点,合并n-1次也就产生了n-1个新节点,所以最终求得的哈夫曼树有2n-1个节点。
哈夫曼树中没有度为1的节点,实际上一棵具有n个叶子节点的哈夫曼树共有2n-1个节点,可以存储在一个大小为2n-1的一维数组中。在构建完哈夫曼树后再求编码需从叶子节点出发走一条从叶子到根的路径。对每个节点我们既需要知道双亲的信息,又需要知道孩子节点的信息。
代码:
#define MAXSIZE 50
#include<string.h>
#include<stdio.h> typedef struct
//定义结构体huffnode,存储节点信息。
{
char data;
//节点值
int weight;
//权值
int parent;
//父节点
int left;
//左节点
int right;
//右节点
int flag;
//标志位
}huffnode;
typedef struct
//定义结构体huffcode,存储节点代码。
{
char code[MAXSIZE];
int start;
}huffcode;
huffnode htree[*MAXSIZE];
huffcode hcode[MAXSIZE]; int select(int i)
//自定义函数select(),用于寻找权值最小的节点。
{
int k=;
int j,q;
for(j=;j<=i;j++)
if(htree[j].weight<k && htree[j].flag==-)
{
k=htree[j].weight;
q=j;
}
htree[q].flag=;
//将找到的节点标志位置1。
return q;
} void creat_hufftree(int n)
//自定义函数creat_hufftree(),用于创建哈夫曼树。
{
int i,l,r;
for(i=;i<*n-;i++)
htree[i].parent=htree[i].left=htree[i].right=htree[i].flag=-;
//全部赋值为-1.
for(i=n;i<*n-;i++)
{
l=select(i-);
r=select(i-);
//找出权值最小的左右节点。
htree[l].parent=i;
htree[r].parent=i;
htree[i].weight=htree[l].weight+htree[r].weight;
//左右节点权值相加等于新节点的权值。
htree[i].left=l;
htree[i].right=r;
}
} creat_huffcode(int n)
//自定义函数creat_huffcode(),用于为各节点进行哈夫曼编码。
{
int i,f,c;
huffcode d;
for(i=;i<n;i++)
{
d.start=n+;
c=i;
f=htree[i].parent;
while(f!=-)
{
if(htree[f].left==c)
//判断c是否是左子树。
d.code[--d.start]='';
//左边编码为0.
else
d.code[--d.start]='';
//右边编码为1.
c=f;
f=htree[f].parent;
}
hcode[i]=d;
}
} void display_huffcode(int n)
//创建函数display_huffcode(),用于输出各节点的编码。
{
int i,k;
printf("huffman is:\n");
for(i=;i<n;i++)
{
printf("%c:",htree[i].data);
//输出节点。
for(k=hcode[i].start;k<=n;k++)
printf("%c",hcode[i].code[k]);
//输出各个节点对应的代码。
printf("\n");
}
} void main()
//定义主函数main(),作为程序的入口,用于输入数据,调用函数。
{
int n=;
htree[].data='a';
htree[].weight=;
htree[].data='b';
htree[].weight=;
htree[].data='c';
htree[].weight=;
htree[].data='d';
htree[].weight=;
htree[].data='e';
htree[].weight=;
htree[].data='f';
htree[].weight=;
creat_hufftree(n);
//调用函数创建哈夫曼树。
creat_huffcode(n);
//调用函数构造哈夫曼编码。
display_huffcode(n);
//显示各节点哈夫曼编码。
}
运行结果:
反思:整个过程,重在理解哈夫曼编码的概念,以及编码方式。如果有兴趣,完全可以将主函数中的固定输入,添加一个输入函数,稍作修改,改为自主输入。当然,如果有学过信息学的同学,更可以将其他编码方式做成程序试试,有利于理解哦。
3.6 图及图的应用
PS:图是一种比线性表和树更为复杂的非线性结构。图结构可以描述各种复杂的数据对象,特别是近年来迅速发展的人工智能、工程、计算科学、语言学、物理、化学等领域中。当然,这些都是套话。说白了,数据结构中复杂度极高的图可以用来处理许多复杂数据,更为贴近现实的一些操作。
由于图片问题,所以只阐述两个例子的合例。
实例107 图的深度优先搜索 实例108 图的广度优先搜索
问题:编程实现如图所示的无向图的深度优先搜索、广度优先搜索。
PS:图片是自己画的,将就一下吧。
逻辑:深度优先搜索即尽可能“深“的遍历一个图,在深度优先搜索中,对于最新已经发现的顶点,如果它的邻接顶点未被访问,则深度优先搜索该邻接顶点。
广度优先遍历是连通图的一种遍历策略。其基本思想如下:
1、从图中某个顶点V0出发,并访问此顶点;
2、从V0出发,访问V0的各个未曾访问的邻接点W1,W2,…,Wk;然后,依次从W1,W2,…,Wk出发访问各自未被访问的邻接点;
3、重复步骤2,直到全部顶点都被访问为止。
概念方面TODD911解释得还是相当清晰的。
代码:
#include<stdio.h>
#include<stdlib.h> struct node
//图的顶点结构
{
int vertex;
int flag;
struct node *nextnode;
};
typedef struct node *graph;
struct node vertex_node[]; #define MAXQUEUE 100
int queue[MAXQUEUE];
int front=-;
int rear=-;
int visited[]; void creat_graph(int *node,int n)
//自定义函数creat_graph(),用于构造邻接表。
{
graph newnode,p;
//定义一个新节点及指针。
int start,end,i;
for(i=;i<n;i++)
{
start=node[i*];
//边的起点。
end=node[i*+];
//边的终点。
newnode=(graph)malloc(sizeof(struct node));
newnode->vertex=end;
//新节点的内容为边终点处顶点的内容。
newnode->nextnode=NULL;
p=&(vertex_node[start]);
//设置指针位置。
while(p->nextnode!=NULL)
p=p->nextnode;
//寻找链尾。
p->nextnode=newnode;
//在链尾处插入新节点。
}
} int enqueue(int value)
//自定义函数enqueue(),用于实现元素入队。
{
if(rear>=MAXQUEUE)
return -;
rear++;
//移动队尾元素
queue[rear]=value;
}
int dequeue()
//自定义函数dequeue(),用于实现元素出队。
{
if(front==rear)
return -;
front++;
//移动队首元素
return queue[front];
} void dfs(int k)
//自定义函数dfs(),用于实现图的深度优先遍历。
{
graph p;
vertex_node[k].flag=;
//将标志位置1,说明该节点已经被访问过了。
printf("vertex[%d]",k);
p=vertex_node[k].nextnode;
//指针指向下一个节点。
while(p!=NULL)
{
if(vertex_node[p->vertex].flag==)
//判断该节点的标志位是否为0
dfs(p->vertex);
//继续遍历下一个节点
p=p->nextnode;
//如果已经遍历过,p指向下一个节点
}
} void bfs(int k)
{
graph p;
enqueue(k);
visited[k]=;
printf("vertex[%d]",k);
while(front!=rear)
//判断是否为空
{
k=dequeue();
p=vertex_node[k].nextnode;
while(p!=NULL)
{
if(visited[p->flag]==)
//判断是否访问过。
{
enqueue(p->vertex);
visited[p->flag]=;
//访问过的元素变为1。
printf("vertexp[%d]",p->vertex);
}
p=p->nextnode;
//访问下一个元素。
}
}
} main()
{
graph p;
int node[],i,sn,vn;
printf("please input the number of sides:\n");
scanf("%d",&sn);
//输入无向图的边数。
printf("please input the number of vertexes:\n");
scanf("%d",&vn);
printf("please input the vertexes which connected by the sides:\n");
for(i=;i<=*sn;i++)
scanf("%d",&node[i]);
//输入每个边所连接的两个顶点,起始及结束位置不同,每边输入两次。
for(i=;i<=vn;i++)
{
vertex_node[i].vertex=i;
//将每个顶点的信息存入数组中
vertex_node[i].flag=;
vertex_node[i].nextnode=NULL;
}
creat_graph(node,*sn);
//调用函数创建邻接表
printf("the result is:\n");
for(i=;i<=vn;i++)
{
printf("vertex%d:",vertex_node[i].vertex);
//将邻接表顶点内容输出
p=vertex_node[i].nextnode;
while(p!=NULL)
{
printf("->%3d",p->vertex);
//输出邻接顶点的内容
p=p->nextnode;
//指针指向下一个邻接顶点
}
printf("\n");
}
printf("the result of depth-first search is:\n");
dfs();
//调用函数进行深度优先遍历
printf("the result of breadth-first search is:\n");
bfs();
//调用函数进行广度优先遍历
printf("\n");
}
反思:其实,主要还是依靠链表那节的思路。所以说,这些数据结构,方法思路都是不变的。重要的还是理解其中的概念,即思想。至于图当中的其他问题,如求最小生成树等问题就不提了。
到这里,数据结构章节已然完结了。
总结:数据结构在数据处理方面有着极其重要的地位。就算是考研,数据结构在计算机专业中,也占到了80',超过了专业分数一半。要知道计算机考研专业150'里可是一共四门棵啊。所以,希望引起足够重视。更为重要的是可以通过数据结构的程序,可以感受到编程中对数据,问题的处理方式,可以说就是编程思想的一点点体现了。
预告:接下来是算法一章。嗯。其实,看到有些书对算法的看法有两种,一种认为算法很重要,是编程的灵魂。另一种认为算法并没有那么重要,更重要的编程的具体实现。我的看法是 算法体现编程思想,编程思想指引算法。
所以,下一章,我更加侧重于思想,而不是具体代码实现。
谢谢。
(表示我已经好几次因为排版问题被踢出首页区了。这次尽力了。真的就这排版水平了。。。。。。)
C语言范例学习03-下的更多相关文章
- C语言范例学习04
第三章 算法 前言:许多人对算法的看法是截然不同的,我之前提到过了.不过,我要说的还是那句话:算法体现编程思想,编程思想指引算法. 同时,有许多人认为简单算法都太简单了,应当去学习一些更为实用的复杂算 ...
- C语言范例学习03-中
栈和队列 这两者都是重要的数据结构,都是线性结构.它们在日后的软件开发中有着重大作用.后面会有实例讲解. 两者区别和联系,其实总结起来就一句.栈,后进先出:队列,先进先出. 可以将栈与队列的存储空间比 ...
- C语言范例学习03-上
第三章 数据结构 章首:不好意思,这两天要帮家里做一些活儿.而且内容量与操作量也确实大幅提升了.所以写得很慢. 不过,从今天开始.我写的东西,许多都是之前没怎么学的了.所以速度会慢下来,同时写得也会详 ...
- C语言范例学习06-上
第六章 文件操作 前言:第五章是C语言在数学上的一些应用,我觉得没有必要,便跳过了.这章正如我标题所写的,是C语言在文件上的操作.学习了这个后,你们可以自行编辑一些所需的快捷程序,来实现一些既定的目的 ...
- 19.go语言基础学习(下)——2019年12月16日
2019年12月16日16:57:04 5.接口 2019年11月01日15:56:09 5.1 duck typing 1. 2. 接口 3.介绍 Go 语言的接口设计是非侵入式的,接口编写者无须知 ...
- C语言范例学习01
编程语言的能力追求T型. 以前学过C语言,但是只学了理论. 从今天开始,我买了本<C语言程序开发范例宝典>.我要把它通关掉. 这应该可以极大地提升我的编程能力. 第一章 基础知识 这章没太 ...
- c语言基础学习03
=============================================================================涉及到的知识点有:编码风格.c语言的数据类型. ...
- C语言范例学习02
第二章 指针 算是重点吧,这也是C语言的特色啊,直接访问物理存储. 重点: 指针就是一个存放它指向变量地址的变量,好绕口. 区分*在定义是与引用是的作用. 区分*.&的不同. 指针 ...
- D03——C语言基础学习PYTHON
C语言基础学习PYTHON——基础学习D03 20180804内容纲要: 1 函数的基本概念 2 函数的参数 3 函数的全局变量与局部变量 4 函数的返回值 5 递归函数 6 高阶函数 7 匿名函数 ...
随机推荐
- Spring3系列4-多个配置文件的整合
Spring3系列4-多个配置文件的整合 在大型的Spring3项目中,所有的Bean配置在一个配置文件中不易管理,也不利于团队开发,通常在开发过程中,我们会按照功能模块的不同,或者开发人员的不同,将 ...
- Silverlight:版本控制的衍化
版本控制是企业开发中一个老生长谈的主题,这也是大部分公司新人进来后需要接纳的一个基础知识体系. 从08年首次接触商业软件编写后,这几年先后接触了SVN,TFS,Git这几个主要的版本控制器,但是并没有 ...
- CSS技巧(二):CSS hack
什么是CSS hack CSS hack由于不同的浏览器,比如IE6,IE7,Firefox等,对CSS的解析认识不一样,因此会导致生成的页面效果不一样,得不到我们所需要的页面效果. 这个时候我们就需 ...
- Object c 基础知识
文件类型说明:.h 头文件,用于定义类.实例变量及类中的方法等定义信息(interface)..m 源文件,定义方法体,可实现objce-c和c方法(implementation)..mm c++源文 ...
- PHP cURL应用实现模拟登录与采集使用方法详解
对于做过数据采集的人来说,cURL一定不会陌生.虽然在PHP中有file_get_contents函数可以获取远程链接的数据,但是它的可控制性太差了,对于各种复杂情况的采集情景,file_get_co ...
- Mac终端常用命令收集
删除非空目录 rm -rf 目录名字 -r 就是向下递归,不管有多少级目录,一并删除-f 就是直接强行删除,不作任何提示的意思 终端修改hosts文件 sudo vi /etc/hosts 切换到su ...
- centos6.5 尝试下用 yum 安装 oddo
我们要安装PostgreSQL,因为OpenERP使用PostgreSQL作为它的数据库.要安装它,我们需要运行下面的命令. yum install postgresql postgresql-ser ...
- easyui menubutton combobox 被遮盖问题
如图一所示,menubutton 中的 combobox 被遮盖 z-Index 不够.这是作者给出的解决方案 <a href="#" class="easyui- ...
- undefined symbol libiconv_open 完全解决方案
我在另一篇关于Ubuntu+Sendmail+Dovecot+Openwebmail 邮件服务器搭建完全解决方案文章完成后,我的邮件服务器也搭建完成了, 事实上也正在运行中, 但是有网友依据我的操作步 ...
- 1、图解Oracle Logminer配置使用
LogMiner配置使用手册 1 Logminer简介 1.1 LogMiner介绍 Oracle LogMiner 是Oracle公司从产品8i以后提供的一个实际非常有用的分析工具,使用该工具可以轻 ...