C语言范例学习03-下

Posted 血夜之末

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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==我的C语言学习进阶之旅解决 Visual Studio 2019 报错:错误 C4996 ‘fscanf‘: This function or variable may be unsafe.(代码片段

我的C语言学习进阶之旅解决 Visual Studio 2019 报错:错误 C4996 ‘fscanf‘: This function or variable may be unsafe.(代码片段

C语言100个经典算法源码片段

C++11多线程 原子操作概念及范例

C内存共享进程通信范例

C语言代码片段