目录
树是由N(N>=0)个结点组成的一个具有层次关系的有限集合,因此树是一种逻辑上为非线性的数据结构。
树的根结点没有前驱结点,除根结点以外的所有结点有且只有一个前驱结点。在树中所有结点可以与零个或者多个后继结点。子树是不相交的,并且在N个结点的树中有N-1条边,因此树是递归定义的。

树最常见的结构是孩子兄弟表示法,每一个结点都有存储数据的变量,指向左边第一个孩子结点的指针和指向右边相邻兄弟结点的指针。
二叉树是一种特殊的树结构,其特点是每个结点至多只有两棵子树(即二叉树中不存在度大于
2 的结点);并且二叉树的子树有左右之分,其次序不能任意颠倒,因此二叉树是有序树。若将其左、右子树颠倒,则成为另一棵不同的二叉树。即使树中结点只有一棵子树,也要区分它是左子树还是右子树。
与树相似,二叉树也以递归的形式定义。二叉树是N(N≥ 0)个结点的有限集合:
二叉树的五种基本形态:
几个特殊的二叉树:



二叉树的性质:

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。所以在实际使用中只有堆才会使用数组来存储。
二叉树的链式存储有两种表示方式,一种是二叉链,也就是每一个结点中有存储数据的变量,指向左孩子的指针与指向右孩子的指针;另一种是三叉链,也就是每一个结点中有存储数据的变量,指向左孩子的指针,指向右孩子的指针以及指向双亲的指针。

堆是一种数据结构,它与操作系统的虚拟进程地址空间中的堆是两回事。堆的逻辑结构是一颗特殊的完全二叉树,它要求双亲结点中的数据要大于或者小于其左右孩子结点中的数据;而堆的物理结构是由动态数组来实现的。
堆可以分为大根堆与小根堆。设数组a存储着堆中的数据,大根堆就是双亲结点的数据大于其左右孩子结点的数据(a i >= a 2*i + 1 && a i >= a 2*i + 2);小根堆就是双亲结点的数据小于其左右孩子结点的数据(a i <= a 2*i + 1 && a i <= a 2*i + 2)。
根据堆的性质我们可以知道,在大根堆中根结点的数据值是最大的,而在小根堆中根结点的数据值是最小的。
堆的基本操作,先用大根堆进行演示,操作小根堆与操作大根堆的思路是一样的,只需要将判断大小条件修改一下就可以进行大小根堆的切换。
因为堆的物理结构是数组,所以堆的初始化与销毁与动态顺序表一样。
入堆的操作就是先将数据插入到数组最后一个位置上,数据插入完成后再进行向上调整。向上调整的基本思路就是让插入的数据与其双亲结点的数据进行比较,如果插入的数据比其双亲结点的数据大,则将插入的数据与双亲数据交换,直到有双亲数据比插入数据大或者插入数据已经换到根结点就停止。

- void HeapPush(Heap* ph, HeapDataType x) // 入堆
- {
- assert(ph);
-
- if (ph->size == ph->capacity) // 检查容量
- {
- int newCapacity = ph->capacity == 0 ? 4 : ph->capacity * 2;
-
- HeapDataType* tmp = (HeapDataType*)realloc(ph->a, sizeof(HeapDataType) * newCapacity);
- if (!tmp)
- {
- perror("realloc fail\n");
- return;
- }
-
- ph->a = tmp;
- ph->capacity = newCapacity;
- }
-
- ph->a[ph->size++] = x; // 先将数据插入数组
-
- AdjustUp(ph->a, ph->size-1); // 进行向上调整
- }
出堆的操作不能直接将根结点的数据删除,否则会把数据打乱,造成堆不再是大根堆,而且再次调整成大根堆的代价很大。正确的出堆操作是将根结点的数据与数组中最后一个结点的数据进行交换,然后将换上来的结点数据与其左右孩子进行调整直到大根堆复原。向下调整的基本思路是先选出左右孩子中数据大的那一个结点(小根堆就是选出小的那一个结点),再将数据大的那一个结点与其双亲结点比较,如果选出来的那一个数据大的孩子结点比双亲结点的数据大则进行交换,直到换上来的结点成了叶子结点或者选出的那一个孩子结点比换上来的结点小就停止交换。

- void HeapPop(Heap* ph) // 出堆
- {
- assert(!HeapEmpty(ph)); // 如果堆为空则不能出堆
- assert(ph);
-
- HeapSwap(&(ph->a[0]), &(ph->a[ph->size - 1])); // 最后一个结点与根结点交换
- ph->size--;
-
- AdjustDown(ph->a, 0, ph->size); // 进行向下调整
- }



堆排序的操作只要用到向上调整与向下调整的思想就可以。堆排序的操作分为两步:第一步建堆,建堆可以向下建堆也可以向上建堆,但向下建堆的效率比向上建堆的效率高。第二步就是排序,将堆顶的元素与最后一个元素交换,交换之后采用向下调整让次大或者次小的元素到堆顶,依次往复,直到有序。所以,如果需要升序就建大根堆,需要降序就建小根堆。
- // 头文件
- typedef int HeapDataType; // 数据类型
-
- typedef struct Heap
- {
- HeapDataType* a; // 数组首地址
- int size; // 数据个数
- int capacity; // 数组容量
- }Heap;
-
- void HeapSwap(HeapDataType* n1, HeapDataType* n2);
- void HeapPrint(Heap* ph);
- void HeapInit(Heap* ph);
- void HeapDestroy(Heap* ph);
- void HeapPush(Heap* ph, HeapDataType x);
- void HeapPop(Heap* ph);
- HeapDataType HeapTop(Heap* ph);
- bool HeapEmpty(Heap* ph);
- int HeapSize(Heap* ph);
- void AdjustDown(HeapDataType* a, int parent, int size);
- void AdjustUp(HeapDataType* a, int child);
- void HeapSort(HeapDataType* a, int size);
-
- // 源文件
- void HeapSwap(HeapDataType* n1, HeapDataType* n2) // 交换结点的数据
- {
- assert(n1 && n2);
-
- HeapDataType tmp = *n1;
- *n1 = *n2;
- *n2 = tmp;
- }
-
- void HeapPrint(Heap* ph) // 打印
- {
- assert(ph);
-
- for (int i = 0; i < ph->size; i++)
- {
- printf("%d ", ph->a[i]);
- }
-
- printf("\n");
- }
-
- void HeapInit(Heap* ph) // 初始化
- {
- assert(ph);
-
- ph->a = NULL;
- ph->size = ph->capacity = 0;
- }
-
- void HeapDestroy(Heap* ph) // 销毁
- {
- assert(ph);
-
- if (ph->a)
- {
- free(ph->a);
- ph->a = NULL;
- ph->size = ph->capacity = 0;
- }
- }
-
- void AdjustUp(HeapDataType* a, int child) // 向上调整
- {
- assert(a);
-
- int parent = (child - 1) / 2; // 获取双亲结点的下标
-
- // while (parent >= 0)这样的条件会多进入一次循环,并且if (a[child] >= a[parent])这样判断会死循环
-
- while (child > 0) // 如果孩子结点的下标小于等于0则停止,调整完成
- {
- if (a[child] >= a[parent]) // 如果插入的数据小于其双亲数据则进行调整否则跳出循环
- {
- HeapSwap(&(a[child]), &(a[parent])); // 交换数据
-
- child = parent; // 迭代
- parent = (child - 1) / 2;
- }
- else
- {
- break;
- }
- }
- }
-
- void HeapPush(Heap* ph, HeapDataType x) // 入堆
- {
- assert(ph);
-
- if (ph->size == ph->capacity) // 检查容量
- {
- int newCapacity = ph->capacity == 0 ? 4 : ph->capacity * 2;
-
- HeapDataType* tmp = (HeapDataType*)realloc(ph->a, sizeof(HeapDataType) * newCapacity);
- if (!tmp)
- {
- perror("realloc fail\n");
- return;
- }
-
- ph->a = tmp;
- ph->capacity = newCapacity;
- }
-
- ph->a[ph->size++] = x; // 先将数据插入数组
-
- AdjustUp(ph->a, ph->size-1); // 进行向上调整
- }
-
- void AdjustDown(HeapDataType* a, int parent, int size) // 向下调整
- {
- assert(a);
-
- int child = parent * 2 + 1; // 先假设左孩子大
-
- while (child < size) // 结束条件不能是while (parent < size)否则会越界
- {
- if ((child + 1 < size) && a[child + 1] > a[child]) // 如果右孩子比左孩子大则孩子下标改为右孩子
- {
- child++;
- }
-
- if (a[child]>a[parent]) // 孩子比双亲大则交换
- {
- HeapSwap(&(a[parent]), &(a[child])); // 交换
-
- parent = child; // 迭代
- child = parent * 2 + 1;
- }
- else
- {
- break;
- }
- }
- }
-
- //void AdjustDown(HeapDataType* a, int parent, int size) // 向下调整--递归
- //{
- // assert(a);
- //
- // int child = parent * 2 + 1;
- //
- // if (child + 1 < size && a[child + 1] < a[child])
- // {
- // child++;
- // }
- //
- // if (child >= size)
- // {
- // return;
- // }
- //
- // if (a[child] < a[parent])
- // {
- // HeapSwap(&(a[child]), &(a[parent]));
- // }
- //
- // AdjustDown(a, child, size);
- //}
-
- void HeapPop(Heap* ph) // 出堆
- {
- assert(!HeapEmpty(ph)); // 如果堆为空则不能出堆
- assert(ph);
-
- HeapSwap(&(ph->a[0]), &(ph->a[ph->size - 1])); // 最后一个结点与根结点交换
- ph->size--;
-
- AdjustDown(ph->a, 0, ph->size); // 进行向下调整
- }
-
- HeapDataType HeapTop(Heap* ph) // 获取根的数据
- {
- assert(!HeapEmpty(ph));
- assert(ph);
-
- return ph->a[0]; // 根的数据就是存储在数组的第一个位置
- }
-
- bool HeapEmpty(Heap* ph) // 判空---若堆为空返回true否则返回false
- {
- assert(ph);
- return ph->size == 0;
- }
-
- int HeapSize(Heap* ph) // 数据元素的个数
- {
- assert(ph);
-
- return ph->size;
- }
-
- void HeapSort(HeapDataType* a, int size) // 堆排序---大根堆
- {
- 建堆---向上建堆 O(N*logN)
- //for (int i = 1; i < size; i++)
- //{
- // AdjustUp(a, i);
- //}
-
- // 建堆---向下建堆 O(N)
- for (int i = (size - 1 - 1) / 2; i >= 0; i--)
- {
- AdjustDown(a, i, size);
- }
-
- // 排序 O(N*logN)
- int end = size - 1;
- while (end > 0)
- {
- HeapSwap(&(a[0]), &(a[end]));
- AdjustDown(a, 0, end);
- end--;
- }
-
- }