C语言核心知识点Day10

本文深入探讨了树和二叉树的概念,包括树的定义、结构特点、左孩子右兄弟表示法以及二叉树的遍历方法。同时介绍了非递归遍历二叉树的栈实现。此外,文章还涵盖了排序算法的基础知识,如冒泡排序、选择排序和插入排序的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.树和二叉树

1.1树的基本概念
树的定义:
	由一个或多个(n≥0)结点组成的有限集合T,有且仅有一个结点称为根(root),
当n>1时,其余的结点分为m(m≥0)个互不相交的有限集合T1,T2,…,Tm。每个集合
本身又是棵树,被称作这个根的子树 。

树的结构特点:
	非线性结构,有一个直接前驱,但可能有多个直接后继(1:n)
	树的定义具有递归性,树中还有树。
	树可以为空,即节点个数为0。
各类名称:
	根  		即根结点(没有前驱)
	叶子    	即终端结点(没有后继)
	森林    	指m棵不相交的树的集合(例如删除A后的子树个数)
	有序树  		结点各子树从左至右有序,不能互换(左为第一)
	无序树  		结点各子树可互换位置。
	双亲   	 	即上层的那个结点(直接前驱) parent
	孩子    	即下层结点的子树 (直接后继) child
	兄弟    	同一双亲下的同层结点(孩子之间互称兄弟)sibling
	堂兄弟  		即双亲位于同一层的结点(但并非同一双亲)cousin
	祖先    	即从根到该结点所经分支的所有结点
	子孙   	    即该结点下层子树中的任一结点
	结点的度  	结点挂接的子树数(有几个直接后继就是几度)
	树深度/高度  指所有结点中最大的层数(Max{各结点的层次})

1.2 左孩子右兄弟表示法
	左孩子右兄弟表示法可以将一颗多叉树转化为一颗二叉树:即节点有两个指针域,
其中一个指针指向子节点,另一个指针指向其兄弟节点

1.3二叉树概念
	1.3.1二叉树基本概念
	定义:
		n(n≥0)个结点的有限集合,由一个根结点以及两棵互不相交的、分别称为
	左子树和右子树的二叉树组成 。
	
	逻辑结构:一对二(1:2) 
	
	基本特征:
		每个结点最多只有两棵子树(不存在度大于2的结点);
		左子树和右子树次序不能颠倒(有序树)。

	二叉树性质:
		性质1: 在二叉树的第i层上至多有2i-1个结点(i>0)
		性质2: 深度为k的二叉树至多有2k-1个结点(k>0)
		性质3: 对于任何一棵二叉树,若度为2的结点数有n2个,则叶子数(n0)
		必定为n2+1 (即n0=n2+1)
		
	满二叉树:一棵深度为k 且有2k -1个结点的二叉树。
	完全二叉树:除最后一层外,每一层上的节点数均达到最大值;在最后一层上
	只缺少右边的若干结点。
	1.3.2 二叉树遍历
		先序:先根、再左子树、再右子树
	    中序:先左、再根子树、再右子树
		后序:先左子树、再右子树、再根
		
1.使用递归的方式遍历二叉树
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//使用递归的方式遍历二叉树
struct BinaryTree
{
	//结点存储的内容
	char c;
	//左
	struct BinaryTree* Lnode;
	//右
	struct BinaryTree* Rnode;
};

//前序
void foreach_BinaryTree1(struct BinaryTree* b)
{
	if (b == NULL)
		return;

	printf("%c", b->c);
	foreach_BinaryTree1(b->Lnode);
	foreach_BinaryTree1(b->Rnode);
}

//中序
void foreach_BinaryTree2(struct BinaryTree* b)
{
	if (b == NULL)
		return;

	foreach_BinaryTree2(b->Lnode);
	printf("%c", b->c);
	foreach_BinaryTree2(b->Rnode);
}

//后序
void foreach_BinaryTree3(struct BinaryTree* b)
{
	if (b == NULL)
		return;

	foreach_BinaryTree3(b->Lnode);
	foreach_BinaryTree3(b->Rnode);
	printf("%c", b->c);
}

void test()
{
	struct BinaryTree b1 = { 'A',NULL,NULL };
	struct BinaryTree b2 = { 'B',NULL,NULL };
	struct BinaryTree b3 = { 'C',NULL,NULL };
	struct BinaryTree b4 = { 'D',NULL,NULL };
	struct BinaryTree b5 = { 'E',NULL,NULL };
	struct BinaryTree b6 = { 'F',NULL,NULL };
	struct BinaryTree b7 = { 'G',NULL,NULL };
	struct BinaryTree b8 = { 'H',NULL,NULL };

	//建立二叉树之间的关系
	b1.Lnode = &b2;
	b1.Rnode = &b6;

	b2.Rnode = &b3;

	b3.Lnode = &b4;
	b3.Rnode = &b5;

	b6.Rnode = &b7;

	b7.Lnode = &b8;

	//遍历	1.前序 2.中序 3.后序
	foreach_BinaryTree1(&b1);
	printf("\n");
	foreach_BinaryTree2(&b1);
	printf("\n");
	foreach_BinaryTree3(&b1);
	printf("\n");
}

int main()
{
	test();
	return 0;
}
2.使用非递归的方式遍历二叉树
利用栈可以实现二叉树的非递归遍历:
	1.给二叉树每个结点设置标志为,默认为0;
	2. 将根节点压入栈中
	3.执行循环(当栈中元素个数大于0时执行)
	4.弹出栈顶元素
	5.如果栈顶标志位真,直接输出并进行下一次循环
	6.如果标志位假  将标志改为真
	7.再将右子树、左子树、根(弹出元素)入栈(这一步决定了遍历方式,由栈的
	先入后出的特性,所示的遍历方式为中序遍历)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//二叉树结点
struct BinaryTree
{
	//结点存储的内容
	char c;
	//左
	struct BinaryTree* Lnode;
	//右
	struct BinaryTree* Rnode;

	//标志位
	int flag;
};

//栈结点
struct Stack
{
	void* buffer[1024];
	int m_size;
};

//初始化栈
struct Stack* init_Stack()
{
	struct Stack* m_stack = malloc(sizeof(struct Stack));
	if (m_stack == NULL)
		return NULL;

	//初始化栈属性
	memset(m_stack->buffer, 0, sizeof(void*) * 1024);
	m_stack->m_size = 0;
	
	return m_stack;
}

//入栈
void push_Stack(struct Stack* m_stack, void* value)
{
	if (m_stack == NULL || value == NULL)
		return;

	//判断是否栈满
	struct Stack* stack1 = m_stack;
	if (stack1->m_size == 1024)
		return;

	//插入元素
	stack1->buffer[stack1->m_size] = value;
	stack1->m_size++;
}

//出栈
void* pop_Stack(struct Stack* m_stack)
{
	if (m_stack == NULL)
		return NULL;

	struct Stack* stack1 = m_stack;
	//栈空
	if (stack1->m_size == 0)
		return NULL;

	//临时变量接收出栈元素
	void* tempValue = stack1->buffer[stack1->m_size - 1];

	//栈内出栈元素置空,栈长度减少
	stack1->buffer[stack1->m_size - 1] = NULL;
	stack1->m_size--;

	return tempValue;
}


//非递归遍历
void foreach_BinaryTree1(struct BinaryTree* b,struct Stack* m_stack)
{
	if (b == NULL )
	{
		printf("错误1\n");
		return;
	}
	if (m_stack == NULL)
	{
		printf("错误2\n");
		return;
	}

	//入栈
	push_Stack(m_stack, b);

	while (m_stack->m_size > 0)
	{
		struct BinaryTree* temp = pop_Stack(m_stack);
		if (temp->flag == 1)
		{
			printf("%c", temp->c);
			continue;
		}
		else
		{
			temp->flag = 1;
			//printf("临时flag的值:%d\n", b->flag);
			push_Stack(m_stack, temp->Rnode);
			push_Stack(m_stack, temp->Lnode);

			push_Stack(m_stack, temp);
		}
	}
}

void test()
{
	struct BinaryTree b1 = { 'A',NULL,NULL ,0};
	struct BinaryTree b2 = { 'B',NULL,NULL ,0 };
	struct BinaryTree b3 = { 'C',NULL,NULL ,0 };
	struct BinaryTree b4 = { 'D',NULL,NULL ,0 };
	struct BinaryTree b5 = { 'E',NULL,NULL ,0 };
	struct BinaryTree b6 = { 'F',NULL,NULL ,0 };
	struct BinaryTree b7 = { 'G',NULL,NULL ,0 };
	struct BinaryTree b8 = { 'H',NULL,NULL ,0 };

	//建立二叉树之间的关系
	b1.Lnode = &b2;
	b1.Rnode = &b6;

	b2.Rnode = &b3;

	b3.Lnode = &b4;
	b3.Rnode = &b5;

	b6.Rnode = &b7;

	b7.Lnode = &b8;

	//非递归遍历二叉树
	struct Stack* m_stack = init_Stack();

	foreach_BinaryTree1(&b1, m_stack);
}

int main()
{
	test();
	return 0;
}
	1.3.3  计算二叉树叶子节点数目以及二叉树的深度
	注:计算二叉树深度时:
		1.根结点左子树高度与根结点右子树高度比较,比较出的最大高度再+1。
		2.若左子树还是树,重复步骤1;若右子树还是树,重复步骤1。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//同样利用递归的方式,不理解可以画一个只有三个结点的二叉树手动推一下就行
struct BinaryTree
{
	//结点存储的内容
	char c;
	//左
	struct BinaryTree* Lnode;
	//右
	struct BinaryTree* Rnode;
};

//叶子结点的判断
void foreach_BinarySonTree(struct BinaryTree* b,int* p)
{
	if (b == NULL)
		return;

	if (b->Lnode == NULL && b->Rnode == NULL)
	{
		(*p)++;
	}
	foreach_BinarySonTree(b->Lnode,p);
	foreach_BinarySonTree(b->Rnode,p);
}

//二叉树层数的判断
int foreach_BinaryHighTree(struct BinaryTree* b)
{
	if (b == NULL)
	{
		return 0;
	}

	//左子树层数
	int lhight = foreach_BinaryHighTree(b->Lnode);
	//左子树层数

	int rlight = foreach_BinaryHighTree(b->Rnode);
	//判断二者大小
	int hight =  lhight > rlight ? lhight + 1 : rlight + 1;
	return hight;
}

void test()
{
	struct BinaryTree b1 = { 'A',NULL,NULL };
	struct BinaryTree b2 = { 'B',NULL,NULL };
	struct BinaryTree b3 = { 'C',NULL,NULL };
	struct BinaryTree b4 = { 'D',NULL,NULL };
	struct BinaryTree b5 = { 'E',NULL,NULL };
	struct BinaryTree b6 = { 'F',NULL,NULL };
	struct BinaryTree b7 = { 'G',NULL,NULL };
	struct BinaryTree b8 = { 'H',NULL,NULL };

	//建立二叉树之间的关系
	b1.Lnode = &b2;
	b1.Rnode = &b6;

	b2.Rnode = &b3;

	b3.Lnode = &b4;
	b3.Rnode = &b5;

	b6.Rnode = &b7;

	b7.Lnode = &b8;

	//统计叶子结点的个数
	int num_son = 0;
	foreach_BinarySonTree(&b1,&num_son);
	printf("叶子结点的个数为:%d\n",num_son);

	//统计层数
	int hight = foreach_BinaryHighTree(&b1);
	printf("计算出层数为:%d\n", hight);
}

int main()
{
	test();
	return 0;
}
	1.3.4 拷贝二叉树
	按以下步骤来即可:	
		1.malloc新结点,
		2.拷贝左子树,拷贝右子树,让新结点连接左子树,右子树。若左子树还
		  是树,重复步骤1、2;若右子树还是树,重复步骤1、2。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

struct BinaryTree
{
	//结点存储的内容
	char c;
	//左
	struct BinaryTree* Lnode;
	//右
	struct BinaryTree* Rnode;
};

//拷贝二叉树
struct BinaryTree* copy_BinaryTree(struct BinaryTree* b)
{
	if (b == NULL)
		return NULL;

	struct BinaryTree* newNode = malloc(sizeof(struct BinaryTree));

	newNode->Lnode = copy_BinaryTree(b->Lnode);
	newNode->Rnode = copy_BinaryTree(b->Rnode);
	newNode->c = b->c;

	return newNode;

}

//前序
void foreach_BinaryTree1(struct BinaryTree* b)
{
	if (b == NULL)
		return;

	printf("%c", b->c);
	foreach_BinaryTree1(b->Lnode);
	foreach_BinaryTree1(b->Rnode);
}

//释放空间
void free_BinaryTree(struct BinaryTree* b)
{
	if (b == NULL)
		return;

	//释放左子树
	free_BinaryTree(b->Lnode);

	//释放右子树
	free_BinaryTree(b->Rnode);

	free(b);
	//b = NULL;
}

void test()
{
	struct BinaryTree b1 = { 'A',NULL,NULL };
	struct BinaryTree b2 = { 'B',NULL,NULL };
	struct BinaryTree b3 = { 'C',NULL,NULL };
	struct BinaryTree b4 = { 'D',NULL,NULL };
	struct BinaryTree b5 = { 'E',NULL,NULL };
	struct BinaryTree b6 = { 'F',NULL,NULL };
	struct BinaryTree b7 = { 'G',NULL,NULL };
	struct BinaryTree b8 = { 'H',NULL,NULL };

	//建立二叉树之间的关系
	b1.Lnode = &b2;
	b1.Rnode = &b6;

	b2.Rnode = &b3;

	b3.Lnode = &b4;
	b3.Rnode = &b5;

	b6.Rnode = &b7;

	b7.Lnode = &b8;

	//拷贝二叉树
	struct BinaryTree* new_BinaryTree = copy_BinaryTree(&b1);

	//遍历二叉树
	foreach_BinaryTree1(new_BinaryTree);

	//释放空间
	free_BinaryTree(new_BinaryTree);

}

int main()
{
	test();
	return 0;
}

2.排序算法

2.1排序基本概念
	2.1.1概念:
		排序是计算机内经常进行的一种操作,其目的是将一组“无序”的数据元素
	调整为“有序”的数据元素。
	
	2.1.2排序的稳定性
		如果在序列中有两个数据元素r[i]和r[j],它们的关键字k[i] == k [j],
	且在排序之前,对象r[i]排在r[j]前面。如果在排序之后,对象r[i]仍在r[j]
	前面,则称这个排序方法是稳定的,否则称这个排序方法是不稳定的。

2.2基本排序算法
	2.2.1 冒泡排序
		冒泡排序是通过内外层循环,对数字进行不断比较,通过一个临时元素来存
	储进行比较的元素,如果符合条件,则进行更换。
	2.2.2 选择排序
		选择排序通过记录当前元素的下标值,在进行数据比较后决定是否更换当前
	下标和之前记录的下标,如果下标更换了,则进行相应的元素交换,否则不交换。
	2.2.3 插入排序
		可以理解为在数组中有一条隐藏的线,一开始这个线的位置在数组的第一个
	元素之后,因此从第二个元素开始取遍历数组,如果数据比较后满足条件利用
	临时值保存住当前值,开始内层循环,内存循环从外层循环减一开始,并且执
	行有比对元素大小的条件,进行数据后移,然后定位到符合条件的位置上去,
	再将之前存储的临时变量赋值给内层循环变量加一的位置上去,因为经过内存
	循环后,内层循环所指的位置是要插入位置的前一个位置。
	代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//以下展示皆为升序排列,如果需要改变顺序,改变数据比较的符号即可
//插入排序
void insertSort(int arr[],int len)
{
	for (int i = 1; i < len; i++)
	{
		if (arr[i] < arr[i - 1])
		{
			int temp = arr[i];
			int j = i-1;
			for (; j >= 0 && temp < arr[j]; j--)
			{
				//数据后移
				arr[j+1] = arr[j];
			}
			//将j+1位置数据赋值给temp,也就是说将小值放入到要插入的指定位置上
			arr[j + 1] = temp;
		}
	}
}

//冒泡排序
void bubbleSort(int arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		for (int j = 0; j < len - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

//选择排序
void selectSort(int arr[],int len)
{
	for (int i = 0; i < len; i++)
	{
		int min = i;
		for (int j = i; j < len; j++)
		{
			if (arr[j] < arr[min])
			{
				min = j;
			}
		}
		if (min != i)
		{
			int temp = arr[min];
			arr[min] = arr[i];
			arr[i] = temp;
		}
	}
}

void test()
{
	int arr[] = { 9,5,6,7,8,3,1,2,4 };
	int len = sizeof(arr) / sizeof(arr[0]);

	//插入排序
	//insertSort(arr, len);
	//冒泡排序
	//bubbleSort(arr, len);
	//选择排序
	selectSort(arr, len);
	//遍历
	for (int i = 0; i < len; i++)
	{
		printf("%d\n", arr[i]);
	}
}

int main()
{
	test();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值