C语言范例学习03-下

树与图

3.5    二叉树及其应用

PS:二叉树是最经典的树形结构,适合计算机处理,具有存储方便和操作灵活等特点,而且任何树都可以转换成二叉树。

实例101  二叉树的递归创建        实例102  二叉树的遍历

问题:编程实现递归创建二叉树,要求显示树的节点内容,深度及叶子节点数。

   构造一棵二叉树,分别采用先序遍历、中序遍历和后序遍历遍历该二叉树。

逻辑:二叉树是一棵由一个根节点(root)和两棵互不相交的分别称作根的左子树和右子树所组成的非空树,左子树和右子树有同样都是一棵二叉树。

   存储二叉树通常采用二叉链表法,即每个节点带两个指针和一个数据域(是不是突然觉得和链表很像呢),一个指针指向左子树,另一个指针指向右子树。

   在遍历二叉树时若先左后右,则有三种遍历方法,分别为先序遍历、中序遍历和后序遍历,它们的遍历定义如下:

    先序遍历:若二叉树非空,则先访问根结点,再按先序遍历左子树,最后按先序遍历右子树。

    中序遍历:若二叉树非空,则先按中序遍历左子树,再访问根结点,最后按中序遍历右子树。

    后序遍历:若二叉树非空,则先按后序遍历左子树,再按后序遍历右子树,最后访问根结点。

代码:

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3
  4 typedef struct node
  5 //二叉链表的结构声明
  6 {
  7     struct node *lchild;
  8     char data;
  9     struct node *rchild;
 10 }bitnode,*bitree;
 11 //结构体的类型。
 12
 13 bitree CreatTree()
 14 //自定义函数CreatTree(),用于构造二叉树。
 15 {
 16     char a;
 17     bitree new;
 18     scanf("%c",&a);
 19     if(a==‘#‘)
 20     //二叉树的输入标识
 21         return NULL;
 22     else
 23     {
 24         new=(bitree)malloc(sizeof(bitnode));
 25         //申请内存空间。
 26         new->data=a;
 27         new->lchild=CreatTree();
 28         new->rchild=CreatTree();
 29     }
 30     return new;
 31 }
 32
 33 void print(bitree bt)
 34 //创建函数print(),用于输出二叉树的节点内容。PS:输出方式为中序遍历。
 35 {
 36     if(bt!=NULL)
 37     {
 38         print(bt->lchild);
 39         printf("%c",bt->data);
 40         print(bt->rchild);
 41     }
 42 }
 43
 44 int btreedepth(bitree bt)
 45 //自定义函数btreedepth(),用于求解二叉树的深度。
 46 {
 47     int ldepth,rdepth;
 48     if(bt==NULL)
 49         return 0;
 50     else
 51     {
 52         ldepth=btreedepth(bt->lchild);
 53         rdepth=btreedepth(bt->rchild);
 54         return(ldepth>rdepth?ldepth+1:rdepth+1);
 55         //返回语句中,采用了三目运算符。不知道的可以看该系列之前的篇章。
 56     }
 57 }
 58
 59 int ncount(bitree bt)
 60 //自定义函数ncount(),用于求二叉树中节点个数。
 61 {
 62     if(bt==NULL)
 63         return 0;
 64     else
 65         return(ncount(bt->lchild)+ncount(bt->rchild)+1);
 66 }
 67
 68 int lcount(bitree bt)
 69 //自定义函数lcount(),用于求二叉树中叶子节点个数。
 70 {
 71     if(bt==NULL)
 72         return 0;
 73     else if(bt->lchild==NULL && bt->rchild==NULL)
 74         return 1;
 75     else
 76         return(lcount(bt->lchild)+lcount(bt->rchild));
 77 }
 78
 79 void preorderTraverse(bitree bt)
 80 //自定义函数preorderTraverse(),用于先序遍历、输出二叉树。
 81 {
 82     if(bt!=NULL)
 83     {
 84         printf("%c",bt->data);
 85         preorderTraverse(bt->lchild);
 86         preorderTraverse(bt->rchild);
 87     }
 88 }
 89
 90 void InorderTraverse(bitree bt)
 91 //自定义函数InorderTraverse(),用于中序遍历、输出二叉树。
 92 {
 93     if(bt!=NULL)
 94     {
 95         InorderTraverse(bt->lchild);
 96         printf("%c",bt->data);
 97         InorderTraverse(bt->rchild);
 98     }
 99 }
100
101 void postorderTraverse(bitree bt)
102 //自定义函数postorderTraverse(),用于后序遍历、输出二叉树。
103 {
104     if(bt!=NULL)
105     {
106         postorderTraverse(bt->lchild);
107         postorderTraverse(bt->rchild);
108         printf("%c",bt->data);
109     }
110 }
111
112 void main()
113 {
114     bitree root;
115     //初始化数据root。
116     root=CreatTree();
117     //调用函数创建二叉链表。
118     printf("contents of binary tree:\n");
119     print(root);
120     //调用函数输出节点内容。
121     printf("\ndepth of binary tree:%d\n",btreedepth(root));
122     //调用函数输出树的深度。
123     printf("the number of the nodes:%d\n",ncount(root));
124     //调用函数输出树中节点个数。
125     printf("the number of the leaf nodes:%d\n",lcount(root));
126     //调用函数输出树中叶子节点个数。
127     printf("preorder traversal:\n");
128     preorderTraverse(root);
129     //调用函数先序遍历输出二叉树。
130     printf("\n");
131     printf("inorder traversal:\n");
132     InorderTraverse(root);
133     //调用函数中序遍历输出二叉树。
134     printf("\n");
135     printf("postorder traversal:\n");
136     postorderTraverse(root);
137     //调用函数后序遍历输出二叉树。
138     printf("\n");
139 }

运行结果:

反思:整个程序代码,并没有多少新鲜东西。重要的是通过程序代码,理解二叉树创建、遍历的概念。

二叉树方面还有线索二叉树、二叉排序树等,但都不会打出代码。因为代码上并没有新的内容,主要是对其概念的理解。

不过,其中的哈夫曼编码还是得说说的。

实例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的一维数组中。在构建完哈夫曼树后再求编码需从叶子节点出发走一条从叶子到根的路径。对每个节点我们既需要知道双亲的信息,又需要知道孩子节点的信息。

代码:

  1 #define MAXSIZE 50
  2 #include<string.h>
  3 #include<stdio.h>
  4
  5 typedef struct
  6 //定义结构体huffnode,存储节点信息。
  7 {
  8     char data;
  9     //节点值
 10     int weight;
 11     //权值
 12     int parent;
 13     //父节点
 14     int left;
 15     //左节点
 16     int right;
 17     //右节点
 18     int flag;
 19     //标志位
 20 }huffnode;
 21 typedef struct
 22 //定义结构体huffcode,存储节点代码。
 23 {
 24     char code[MAXSIZE];
 25     int start;
 26 }huffcode;
 27 huffnode htree[2*MAXSIZE];
 28 huffcode hcode[MAXSIZE];
 29
 30 int select(int i)
 31 //自定义函数select(),用于寻找权值最小的节点。
 32 {
 33     int k=32767;
 34     int j,q;
 35     for(j=0;j<=i;j++)
 36         if(htree[j].weight<k && htree[j].flag==-1)
 37         {
 38             k=htree[j].weight;
 39             q=j;
 40         }
 41     htree[q].flag=1;
 42     //将找到的节点标志位置1。
 43     return q;
 44 }
 45
 46 void creat_hufftree(int n)
 47 //自定义函数creat_hufftree(),用于创建哈夫曼树。
 48 {
 49     int i,l,r;
 50     for(i=0;i<2*n-1;i++)
 51         htree[i].parent=htree[i].left=htree[i].right=htree[i].flag=-1;
 52         //全部赋值为-1.
 53     for(i=n;i<2*n-1;i++)
 54     {
 55         l=select(i-1);
 56         r=select(i-1);
 57         //找出权值最小的左右节点。
 58         htree[l].parent=i;
 59         htree[r].parent=i;
 60         htree[i].weight=htree[l].weight+htree[r].weight;
 61         //左右节点权值相加等于新节点的权值。
 62         htree[i].left=l;
 63         htree[i].right=r;
 64     }
 65 }
 66
 67 creat_huffcode(int n)
 68 //自定义函数creat_huffcode(),用于为各节点进行哈夫曼编码。
 69 {
 70     int i,f,c;
 71     huffcode d;
 72     for(i=0;i<n;i++)
 73     {
 74         d.start=n+1;
 75         c=i;
 76         f=htree[i].parent;
 77         while(f!=-1)
 78         {
 79             if(htree[f].left==c)
 80             //判断c是否是左子树。
 81                 d.code[--d.start]=‘0‘;
 82                 //左边编码为0.
 83             else
 84                 d.code[--d.start]=‘1‘;
 85                 //右边编码为1.
 86             c=f;
 87             f=htree[f].parent;
 88         }
 89         hcode[i]=d;
 90     }
 91 }
 92
 93 void display_huffcode(int n)
 94 //创建函数display_huffcode(),用于输出各节点的编码。
 95 {
 96     int i,k;
 97     printf("huffman is:\n");
 98     for(i=0;i<n;i++)
 99     {
100         printf("%c:",htree[i].data);
101         //输出节点。
102         for(k=hcode[i].start;k<=n;k++)
103             printf("%c",hcode[i].code[k]);
104             //输出各个节点对应的代码。
105         printf("\n");
106     }
107 }
108
109 void main()
110 //定义主函数main(),作为程序的入口,用于输入数据,调用函数。
111 {
112     int n=6;
113     htree[0].data=‘a‘;
114     htree[0].weight=18;
115     htree[1].data=‘b‘;
116     htree[1].weight=20;
117     htree[2].data=‘c‘;
118     htree[2].weight=4;
119     htree[3].data=‘d‘;
120     htree[3].weight=13;
121     htree[4].data=‘e‘;
122     htree[4].weight=16;
123     htree[5].data=‘f‘;
124     htree[5].weight=48;
125     creat_hufftree(n);
126     //调用函数创建哈夫曼树。
127     creat_huffcode(n);
128     //调用函数构造哈夫曼编码。
129     display_huffcode(n);
130     //显示各节点哈夫曼编码。
131 }

运行结果:

反思:整个过程,重在理解哈夫曼编码的概念,以及编码方式。如果有兴趣,完全可以将主函数中的固定输入,添加一个输入函数,稍作修改,改为自主输入。当然,如果有学过信息学的同学,更可以将其他编码方式做成程序试试,有利于理解哦。

3.6      图及图的应用

PS:图是一种比线性表和树更为复杂的非线性结构。图结构可以描述各种复杂的数据对象,特别是近年来迅速发展的人工智能、工程、计算科学、语言学、物理、化学等领域中。当然,这些都是套话。说白了,数据结构中复杂度极高的图可以用来处理许多复杂数据,更为贴近现实的一些操作。

   由于图片问题,所以只阐述两个例子的合例。

实例107    图的深度优先搜索        实例108    图的广度优先搜索

问题:编程实现如图所示的无向图的深度优先搜索、广度优先搜索。

   

    PS:图片是自己画的,将就一下吧。

逻辑:深度优先搜索即尽可能“深“的遍历一个图,在深度优先搜索中,对于最新已经发现的顶点,如果它的邻接顶点未被访问,则深度优先搜索该邻接顶点。  

   广度优先遍历是连通图的一种遍历策略。其基本思想如下:

    1、从图中某个顶点V0出发,并访问此顶点;

    2、从V0出发,访问V0的各个未曾访问的邻接点W1,W2,…,Wk;然后,依次从W1,W2,…,Wk出发访问各自未被访问的邻接点;

    3、重复步骤2,直到全部顶点都被访问为止。

   概念方面TODD911解释得还是相当清晰的。

代码:

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3
  4 struct node
  5 //图的顶点结构
  6 {
  7     int vertex;
  8     int flag;
  9     struct node *nextnode;
 10 };
 11 typedef struct node *graph;
 12 struct node vertex_node[10];
 13
 14 #define MAXQUEUE 100
 15 int queue[MAXQUEUE];
 16 int front=-1;
 17 int rear=-1;
 18 int visited[10];
 19
 20 void creat_graph(int *node,int n)
 21 //自定义函数creat_graph(),用于构造邻接表。
 22 {
 23     graph newnode,p;
 24     //定义一个新节点及指针。
 25     int start,end,i;
 26     for(i=0;i<n;i++)
 27     {
 28         start=node[i*2];
 29         //边的起点。
 30         end=node[i*2+1];
 31         //边的终点。
 32         newnode=(graph)malloc(sizeof(struct node));
 33         newnode->vertex=end;
 34         //新节点的内容为边终点处顶点的内容。
 35         newnode->nextnode=NULL;
 36         p=&(vertex_node[start]);
 37         //设置指针位置。
 38         while(p->nextnode!=NULL)
 39             p=p->nextnode;
 40             //寻找链尾。
 41         p->nextnode=newnode;
 42         //在链尾处插入新节点。
 43     }
 44 }
 45
 46 int enqueue(int value)
 47 //自定义函数enqueue(),用于实现元素入队。
 48 {
 49     if(rear>=MAXQUEUE)
 50         return -1;
 51     rear++;
 52     //移动队尾元素
 53     queue[rear]=value;
 54 }
 55 int dequeue()
 56 //自定义函数dequeue(),用于实现元素出队。
 57 {
 58     if(front==rear)
 59         return -1;
 60     front++;
 61     //移动队首元素
 62     return queue[front];
 63 }
 64
 65 void dfs(int k)
 66 //自定义函数dfs(),用于实现图的深度优先遍历。
 67 {
 68     graph p;
 69     vertex_node[k].flag=1;
 70     //将标志位置1,说明该节点已经被访问过了。
 71     printf("vertex[%d]",k);
 72     p=vertex_node[k].nextnode;
 73     //指针指向下一个节点。
 74     while(p!=NULL)
 75     {
 76         if(vertex_node[p->vertex].flag==0)
 77         //判断该节点的标志位是否为0
 78             dfs(p->vertex);
 79             //继续遍历下一个节点
 80         p=p->nextnode;
 81         //如果已经遍历过,p指向下一个节点
 82     }
 83 }
 84
 85 void bfs(int k)
 86 {
 87     graph p;
 88     enqueue(k);
 89     visited[k]=1;
 90     printf("vertex[%d]",k);
 91     while(front!=rear)
 92     //判断是否为空
 93     {
 94         k=dequeue();
 95         p=vertex_node[k].nextnode;
 96         while(p!=NULL)
 97         {
 98             if(visited[p->flag]==0)
 99             //判断是否访问过。
100             {
101                 enqueue(p->vertex);
102                 visited[p->flag]=1;
103                 //访问过的元素变为1。
104                 printf("vertexp[%d]",p->vertex);
105             }
106             p=p->nextnode;
107             //访问下一个元素。
108         }
109     }
110 }
111
112 main()
113 {
114     graph p;
115     int node[100],i,sn,vn;
116     printf("please input the number of sides:\n");
117     scanf("%d",&sn);
118     //输入无向图的边数。
119     printf("please input the number of vertexes:\n");
120     scanf("%d",&vn);
121     printf("please input the vertexes which connected by the sides:\n");
122     for(i=1;i<=4*sn;i++)
123         scanf("%d",&node[i]);
124         //输入每个边所连接的两个顶点,起始及结束位置不同,每边输入两次。
125     for(i=1;i<=vn;i++)
126     {
127         vertex_node[i].vertex=i;
128         //将每个顶点的信息存入数组中
129         vertex_node[i].flag=0;
130         vertex_node[i].nextnode=NULL;
131     }
132     creat_graph(node,2*sn);
133     //调用函数创建邻接表
134     printf("the result is:\n");
135     for(i=1;i<=vn;i++)
136     {
137         printf("vertex%d:",vertex_node[i].vertex);
138         //将邻接表顶点内容输出
139         p=vertex_node[i].nextnode;
140         while(p!=NULL)
141         {
142             printf("->%3d",p->vertex);
143             //输出邻接顶点的内容
144             p=p->nextnode;
145             //指针指向下一个邻接顶点
146         }
147         printf("\n");
148     }
149     printf("the result of depth-first search is:\n");
150     dfs(1);
151     //调用函数进行深度优先遍历
152     printf("the result of breadth-first search is:\n");
153     bfs(1);
154     //调用函数进行广度优先遍历
155     printf("\n");
156 }

反思:其实,主要还是依靠链表那节的思路。所以说,这些数据结构,方法思路都是不变的。重要的还是理解其中的概念,即思想。至于图当中的其他问题,如求最小生成树等问题就不提了。

到这里,数据结构章节已然完结了。

总结:数据结构在数据处理方面有着极其重要的地位。就算是考研,数据结构在计算机专业中,也占到了80‘,超过了专业分数一半。要知道计算机考研专业150‘里可是一共四门棵啊。所以,希望引起足够重视。更为重要的是可以通过数据结构的程序,可以感受到编程中对数据,问题的处理方式,可以说就是编程思想的一点点体现了。

预告:接下来是算法一章。嗯。其实,看到有些书对算法的看法有两种,一种认为算法很重要,是编程的灵魂。另一种认为算法并没有那么重要,更重要的编程的具体实现。我的看法是   算法体现编程思想,编程思想指引算法。

所以,下一章,我更加侧重于思想,而不是具体代码实现。

谢谢。

(表示我已经好几次因为排版问题被踢出首页区了。这次尽力了。真的就这排版水平了。。。。。。)

时间: 2024-11-07 12:48:31

C语言范例学习03-下的相关文章

C语言范例学习04

第三章 算法 前言:许多人对算法的看法是截然不同的,我之前提到过了.不过,我要说的还是那句话:算法体现编程思想,编程思想指引算法. 同时,有许多人认为简单算法都太简单了,应当去学习一些更为实用的复杂算法.不过,许多复杂算法都是从简单算法演绎而来的,这里就不一一举例了.而且,算法千千万万.更为重要的是从算法中体会编程的思想. 4.1 简单问题算法 PS:接下来是一些入门问题,简单到都没有具体算法可言.但这些问题是我们当初对算法的入门.如果不喜欢,可以跳过. 实例111 任意次方后的最后三位 问题:

C语言范例学习03-中

这几天不得不说,真的好热.表示我这个不喜欢吹空调的人都老老实实蹲进空调房了. 这下讲的是栈和队列. 这两者都是重要的数据结构,都是线性结构.它们在日后的软件开发中有着重大作用.后面会有实例讲解. 两者区别和联系,其实总结起来就一句.栈,后进先出:队列,先进先出. 可以将栈与队列的存储空间比作一个只够一个身位的水管. 栈的水管,有一头被堵住了.所以当人们进去后,想出来就只能让最靠近出口的那位先出去,依次推出.(后进先出). 队列的水管,类似单向车道.所以当人们进去后,想出来就只能一直向前,走出来,

UNIX/Linux下C语言的学习路线

一.工具篇 “公欲善其事,必先利其器”.编程是一门实践性很强的工作,在你以后的学习或工作中,你将常常会与以下工具打交道, 下面列出学习C语言编程常常用到的软件和工具. 1.操作系统    在UNIX或Linux系统中学习C很方便,所以在开始您的学习旅程前请先选择一个UNIX或Linux操作系统,目前可供个人免费使用的UNIX或Linux系统有FreeBSD.RedHat Linux.SUSE Linux等,而且在安装包中还提供很多实用的工具,如:gcc, make等. 如果您一直使用Window

RabbitMQ (消息队列)专题学习03 Work Queues(工作队列)

一.概述 工作队列(Work queues) (使用Java客户端) 在前面的专题学习中,我们使用Java语言实现了一个简单的从名为"hello"的队列中发送和接收消息的程序,在这部内容中我们将创建一个工作队列被用来分配定时消息任务,而且通过多个接收者(工作者)实现. 工作队列(又名任务队列),主要的思想是为了避免立即做一个资源密集型的任务(多消息同时密集发送),不必等待它完成,当运行许多工作者的让任务都在她们之间共享. 它在web应用中是非常有用的,因为在很短的时间内http请求窗口

D03——C语言基础学习PYTHON

C语言基础学习PYTHON--基础学习D02 20180804内容纲要: 1 函数的基本概念 2 函数的参数 3 函数的全局变量与局部变量 4 函数的返回值 5 递归函数 6 高阶函数 7 匿名函数 8 函数式编程 9 小结 10 小练习:ATM+购物车程序 1 函数的基本概念 a 函数定义 b 函数特性 c 定义函数 a 定义:函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可. b 特性: 减少重复代码 使程序变的可扩展 使程序变得易维护 c:语法

转 学习linux下的c/c++编程

http://blog.csdn.net/byxdaz/article/details/3959680 1,先有linux环境搭minGW和cygwin都有点麻烦,最最简单的办法还是装个真正的linux,用虚拟机也好,在网络上的另一台机器也好.这样不仅快,而且你有了真正的环境.2.会C/C++语言(估计你会的)3.入门阶段熟悉gcc命令行,最基本的参数,如,-g,-W,-O,-o,-c 建议看man gcc(很大找想要的)4.编译第一个helloworld程序: 基本命令 gcc hellowo

ThinkPhp学习03

原文:ThinkPhp学习03 一.ThinkPHP 3 的输出      (重点) a.通过 echo 等PHP原生的输出方式在页面中输出 b.通过display方法输出   想分配变量可以使用assign方法 public function index(){ $name="潘达"; $this->assign('name',$name); //将$name分配给变量name,从而让模板获得 $this->display(); } 模板获取 在对应的tpl下创建模块文件夹

c语言指针学习

前言 近期俄罗斯的陨石.四月的血月.五月北京的飞雪以及天朝各种血腥和混乱,给人一种不详的预感.佛祖说的末法时期,五浊恶世 ,十恶之世,人再无心法约束,道德沦丧,和现在正好吻合.尤其是在天朝,空气,水,食品,你能告诉还有没有问题的吗?不知大难至,世人依旧忙.祸福相依,危中有机.那些高级生命,出于慈悲,会救渡我们,但是你要去思考,去发现机缘. 最近较闲,没事就学点基础知识,整天在上层晃,感觉晕的厉害,接地气.关于指针我上学的时候学过一点,我的老师说"指针很难呢",当时以为这老师挺谦虚的.后

C语言在VS2017环境下写俄罗斯方块的感悟

C语言给几乎所有人的印象就是语法太严格.使用太拘谨,错误特别多,纠结太细致.很难提起兴趣来.同时还有如下的巨大弱势: 1:C语言写了很久还是只开发黑窗口程序,看不到实际应用在哪里,严重打击兴趣: 2:如果想要使用高效的集成开发环境,却很难和图形库链接上(主要是太多横空出世.突如其来的.不懂的操作细节,比如链接Lib库,为什么要那样做?不清楚,只是照着做而已):以至于高效IDE的魅力也大大失去了光彩. 3:硬要写图形化窗口你可能还真得跑到TC这种连复制粘贴都不友好的环境下去玩.实际公司中不用这低级