由于网上的数据结构大都以C++,或是更抽象的伪代码呈现,造成纯c入门新手学数据结构十分苦恼,特此给出学习过程的一段代码笔记,以帮助c新手入门数据结构
本段代码可通过vs2019编译通过运行
输入样式
10
8 15 3 4 1 5 12 10 18 6
输出结果
8 3 15 1 4 12 18 5 10 6
#pragma warning(disable:4996)
#include <stdio.h>
#include<stdlib.h>//从而使用malloc函数
#define max 30
typedef struct BTNode {
int key;
struct BTNode* lchild;
struct BTNode* rchild;
}BiTree;
BiTree* BstInsert(BiTree* T, int k);
void PrintTree(BiTree* root);
int main() {
int number = 0;
scanf("%d", &number);
int a[30], i = 1;//vs不支持数组用变量定义
for (; i <= number; i++)
scanf("%d", &a[i]);
BiTree* root = NULL;//定义头指针
int o = 1;
while (o <= number) {
root = BstInsert(root, a[o]);
//由于root会在函数中改变,并要传递回去,因此要root=,而不能单独挂函数
//传递的是指针本身,因此也不用&,进行取地址多此一举
o++;
}
PrintTree(root);//有借有还再借不难
return 0;
}
BiTree* BstInsert(BiTree* T, int k) {//返回的root是一个指针类型,因此要*
if (T == NULL) {
T = (BiTree*)malloc(sizeof(BiTree));//malloc默认返回void,因此要强制类型转换
T->key = k;
T->lchild = T->rchild = NULL;
}
else if (k < T->key)
T->lchild = BstInsert(T->lchild, k);
else if (k > T->key)
T->rchild = BstInsert(T->rchild, k);
return T;//这句很重要。。要把改变/连起来的树传递回去
}
void PrintTree(BiTree* T) {
int front = 0, rear = 0;
BiTree* b[max];
//定义并初始化
//一个BiTree指针队列(用来存放BiTree指针)“企业级代码”
BiTree* q;//指针临时存放点
if (T != NULL) {
rear = (rear + 1) % max;
b[rear] = T;
//根结点入队
while (front != rear) {//队列不空循环
front = (front + 1) % max;
q = b[front];
//出队
printf("%d ", q->key);
//访问
if (q->lchild != NULL) {
rear = (rear + 1) % max;
b[rear] = q->lchild;
}
//左子树根节点入队
if (q->rchild != NULL) {
rear = (rear + 1) % max;
b[rear] = q->rchild;
}
//右子树根节点入队
}
}
}
层序输出过程解析
未进入循环前,rear将根节点存放在b1,
进入1轮循环
(front读出)根节点交给临时指针q,同时输出该指针的key
在然后将根结点的2个儿子存放在b2,b3
2轮循环
输出b2(根节点左儿子)
并将其2个儿子存放在b4,b5
3轮循环
输出b3(根节点右儿子)
并将其2个儿子存放在b6,b7
因此 front是输出端,rear是进入端
每次循环输出1个,输入至多2个
当front和rear贴到一起,则循环结束(说明没有输入了)
题外话1
为何我说 结构指针 是“企业级代码”
原因在于目前层序输出大多采用 王道书上那种偏模块化函数的写法
个人理解如果只是做个层序输出,完全没有必要嵌套那么多层函数,写起来相对繁琐。上述写法相对更清晰明了
而这种技巧实质是十分常见的。
详情可看 中国大学MOOC《翁恺老师——C语言程序结构设计》第14周链表第3课 其中有讲到返回结构类型的3种办法
题外话2
c入坑多少还是要再学C++的,毕竟C++直接封装了队列,栈这类常用函数,相对程序运行也会更快更稳定,写起来也容易,记忆也会更容易
STL库函数层序输出版本
相对而言更为简洁,更容易理解
#include<queue>
using namespace std;//头上加一段
void PrintTree(BiTree* T) {
queue<BiTree*> b;
if (T != NULL) {
b.push(T);
while (b.empty() != true) {//队列不空循环
if (b.front()->lchild != NULL)
b.push(b.front()->lchild);
//不空则左子树入队
if (b.front()->rchild != NULL)
b.push(b.front()->rchild);
printf("%d ", b.front()->key);//输出队头元素(队头也就是先进的)
b.pop();//当父结点的2个子结点全都入队,并print过了,任务完成出队
}
}
}
如果有心去内存看STL的队列实现方式
会发现这种队列确实会更高效
原因他实现更像是链表,可以进行动态的删除(出队)插入(入队)
而传统的队列实现更多是依靠数组,所谓插入删除也只是数组下标的变动罢了,本质是数据还是存在内存,占据了空间
当然队列也是可以构造链表来实现,但是相应的插入删除也变得更为复杂