一、完全二叉树
1.用递归法实现完全二叉树的创建和遍历
头文件:
cpp
typedef struct node
{
int No;
struct node *pLeftChild;
struct node *pRightChild;
}TreeNode_t;
(1)创建:(基于前序遍历的思想)
关键代码示例:
cpp
TreeNode_t *CreateCompleteTree(int StartNo, int EndNo)
{
Treenode_t *pNewNode = NULL;
if(StartNo > EndNo)
{
return NULL;
}
pNewNode = malloc(sizeof(TreeNode_t));
if(NULL == pNewNode)
{
perror("fail to malloc");
return NULL;
}
pNewNode->No = StartNo;
pNewNode->pLeftChild = CreateCompleteTree(2 * startNo, EndNo);
pNewNode->pRightChild = CreateCompleteTree(2 * StartNo + 1, EndNo);
return pNewNode;
}
(2)前序遍历(根左右)
关键代码示例:
cpp
void PreOrderTree(TreeNode_t *pTmpRoot)
{
printf("%d ", pTmpRoot->No);
if(pTmpRoot->pLeftChild != NULL)
{
PreOrderTree(pTmpRoot->pLeftChild);
}
if(pTmpRoot->pRightChild != NULL)
{
PreOrderTree(pTmpRoot->pRightChild);
}
return;
}
(3)中序遍历(左根右)
关键代码示例:
cpp
void InOrderTree(TreeNode_t *pTmpRoot)
{
if(pTmpRoot->pLeftChild != NULL)
{
PreOrderTree(pTmpRoot->pLeftChild);
}
printf("%d ", pTmpRoot->No);
if(pTmpRoot->pRightChild != NULL)
{
PreOrderTree(pTmpRoot->pRightChild);
}
return;
}
(4)后序遍历
关键代码示例:
cpp
void PostOrderTree(TreeNode_t *pTmpRoot)
{
if(pTmpRoot->pLeftChild != NULL)
{
PreOrderTree(pTmpRoot->pLeftChild);
}
if(pTmpRoot->pRightChild != NULL)
{
PreOrderTree(pTmpRoot->pRightChild);
}
printf("%d ", pTmpRoot->No);
return;
}
(5)销毁
将申请到的空间全部销毁(利用后序遍历的思想)
关键代码示例:
cpp
int DestroyTree(Tree_Node **ppTmpRoot)
{
if((*ppTmpRoot)->pLeftChild != NULL)
{
DestoryTree((*ppTmpRoot)->pLeftChild);
}
if((*ppTmpRoot)->pRightChild != NULL)
{
DestoryTree((*ppTmpRoot)->pRightChild);
}
free(*ppTmpRoot);
*ppTmpRoot = NULL;
return 0;
}
(6)层序遍历
广度优先遍历,利用队列的思想
- 创建一个队列(这里用链式队列,直接用之前写过的);
- 将编号为1的节点(根节点)入队,入队再出队,判断有无左右孩子,若有,则将左右孩子一起入队,入队之前打印,再出队判断有无左右孩子,同样的操作循环,直到队列为空。
关键代码示例:
cpp
int LayOutOrderTree(TreeNode_t *pTmpRoot)
{
Node_t *pTmpQueue = NULL;
DataType pTmpNode = NULL;
if(NULL == pTmpRoot)
{
return 0;
}
pTmpQueue = CreateEmptyLinkQueue();
EnterLinkQueue(pTmpQueue, pTmpRoot)
while(!IsEmptyLinkQueue(pTmpQueue))
{
pTmpNode = QuitLinkQueue(pTmpQueue);
printf("%d ", pTmpNode->No);
if(pTmpNode->pLeftChild != NULL)
{
EnterLinkQueue(pTmpQueue, pTmpNode->pleftChild);
}
if(pTmpNode->pRightChild != NULL)
{
EnterLinkQueue(pTmpQueue, pTmpNode->pRightChild);
}
}
DestoryLinkQueue(&pTmpQueue);
return 0;
}
二、非完全二叉树
头文件:
cpp
typedef struct node
{
char Data;
int No;
int Flag;
struct node *pLeftChild;
struct node *pRightChild;
}TreeNode_t;
1.创建
- 手动输入要创建的非完全二叉树的编号(无左右孩子用'#'代替),例如:
- A B # # C D F # # # E # #
- 申请空间,创建节点,再赋值。
关键代码示例:
cpp
TreeNode_t CreateTree(int No)
{
char ch = 0;
TreeNode_t *pNewNode = NULL;
scanf(" %c", &ch);
if('#' == ch)
{
return NULL;
}
else
{
pNewNode = malloc(sizeof(TreeNode_t));
if(pNewNode == NULL)
{
perror("fail to malloc");
return NULL;
}
pNewNode->No = No;
pNewNode->Data = ch;
pNewNode->pLeftChild = CreateTree(2 * No);
pNewNode->pRightChild = CreateTree(2 * No + 1);
}
return pNewNode;
}
2.非递归法实现遍历
深度优先遍历,利用栈的思想
(1)前序遍历(入栈前打印)
创建一个空栈,从根节点开始,将左孩子全都入栈,入栈前打印,最后一个入栈的先出,再将右孩子入栈,循环遍历,直到没有节点可入栈。
关键代码示例:
cpp
void PreOrderTreeByStack(TreeNode_t *pTmpRoot)
{
Stack_t pTmpStack = NULL;
DataType TmpNode = NULL;
pTmpStack = CreateEmptyStack(50);
pTmpNode = pTmpRoot;
while(1)
{
while(pTmpNode != NULL)
{
printf("%d(%c) ", pTmpNode->No, pTmpNode->Data);
PushStack(pTmpStack, pTmpNode);
pTmpNode = pTmpNode->pLeftChild;
}
if(IsEmptyStack(pTmpStack))
{
break;
}
pTmpNode = PopStack(pTmpStack);
pTmpNode = pTmpNode->pRightChild;
}
DestoryStack(&pTmpStack);
return;
}
(2)中序遍历(出栈时打印)
创建一个空栈,从根节点开始,将左孩子全都入栈,最后一个入栈的先出,出栈时打印,再将右孩子入栈,循环遍历,直到没有节点可入栈。
关键代码示例:
cpp
void PreOrderTreeByStack(TreeNode_t *pTmpRoot)
{
Stack_t pTmpStack = NULL;
DataType TmpNode = NULL;
pTmpStack = CreateEmptyStack(50);
pTmpNode = pTmpRoot;
while(1)
{
while(pTmpNode != NULL)
{
PushStack(pTmpStack, pTmpNode);
pTmpNode = pTmpNode->pLeftChild;
}
if(IsEmptyStack(pTmpStack))
{
break;
}
pTmpNode = PopStack(pTmpStack);
printf("%d(%c) ", pTmpNode->No, pTmpNode->Data);
pTmpNode = pTmpNode->pRightChild;
}
DestoryStack(&pTmpStack);
return;
}
(3)后序遍历(每个节点入两次栈,第一次出栈判断,第二次出栈打印)
创建一个空栈,从根节点开始,将左孩子全都入栈,最后一个入栈的先出,第一次出栈判断是否有左右孩子,再入栈,若无左右孩子,则第二次入栈后,出栈时打印;若有左右孩子,则再将左右孩子入栈,循环操作,直到没有节点可入栈。
关键代码示例:
cpp
void PostOrderTreeByStack(TreeNode_t *pTmpRoot)
{
Stack_t pTmpStack = NULL;
DataType TmpNode = NULL;
pTmpStack = CreateEmptyStack(50);
pTmpNode = pTmpRoot;
while(1)
{
while(pTmpNode != NULL)
{
pTmpNode->Flag = 1;
PushStack(pTmpStack, pTmpNode);
pTmpNode = pTmpNode->pLeftChild;
}
if(IsEmptyStack(pTmpStack))
{
break;
}
pTmpNode = PopStack(pTmpStack);
if(1 == pTmpNode->Flag)
{
pTmpNode->Flag = 0;
PushStack(pTmpStack, pTmpNode);
pTmpNode = pTmpNode->pRightChild;
}
else if(0 == pTmpNode->Flag)
{
printf("%d(%c) ", pTmpNode->No, pTmpNode->Data);
pTmpNode = NULL;
}
}
DestoryStack(&pTmpStack);
return;
}
三、哈希表
-
- 哈希:算法思想,将数据根据哈希算法映射成键值,根据键值来写入或是查找数据,以实现查找数据O(1)时间复杂度;
-
- 哈希碰撞(哈希冲突):多个数据通过哈希算法映射成同样的键值,说明产生哈希冲突;
-
- 链地址法:数据产生哈希冲突通过在同一键值位置用链表实现多个数据的存储;
示例:将0 - 100数据存放在哈希表中
头文件:
cpp
typedef struct node
{
int Data;
struct node *pNext;
}Node_t;
1.创建
当不同数据通过哈希函数计算出相同索引 时,就会发生哈希冲突 ,代码中采用链地址法(开散列)解决,且对每个桶的链表做升序有序插入,既解决冲突,又让链表保持有序性。
(1)通过二级指针遍历实现,步骤为:
- 以哈希索引为起点,用二级指针
ppTmpNode指向头指针(&pHashTable[Index]); - 遍历链表:若当前节点非空且待插入数据大于当前节点数据,继续向后遍历;
- 遍历结束时,
ppTmpNode指向待插入位置的前驱指针 (可能是头指针,也可能是链表中某个节点的pNext)。
(2)确定插入位置后,完成新节点内存分配 和链表插入:
- 用
malloc分配新节点内存,检查分配结果,失败则用perror打印错误信息并返回 - 1; - 给新节点赋值:数据域存待插入数据,指针域指向当前待插入位置的节点(
*ppTmpNode); - 将前驱指针(
*ppTmpNode)指向新节点,完成插入。
关键代码示例:
cpp
static Node_t *pHashTable[10];
int InsertHashTable(int TmpData)
{
int Index = 0;
Node_t *pNewNode = NULL;
Node_t **ppTmpNode = NULL;
Index = TmpData % 10;
for(ppTmpNode = &pHashTable[Index]; *ppTmpNode != NULL && TmpData > (*ppTmpNode)->Data; ppTmpNode = &(*ppTmpNode)->pNext);
pNewNode = malloc(sizeof(Node_t));
if(NULL == pNewNode)
{
perror("fail to malloc");
return -1;
}
pNewNode->Data = TmpData;
pNewNode->pNext = *ppTmpNode;
*ppTmpNode = pNewNode;
return 0;
}
2.遍历(打印)
通过双层循环遍历哈希表的所有数据:
- 外层循环遍历哈希表的所有位置(0~9);
- 内层循环遍历每个位置对应的链表,依次打印节点数据;
- 按
索引: 数据1 数据2 ...的格式输出,直观展示每个桶的链表数据分布。
关键代码示例:
cpp
void ShowHashTable(void)
{
int i = 0;
Node_t *pTmpNode = NULL;
for(i = 0; i < 10; i++)
{
printf("%d:", i);
pTmpNode = pHashTable[i];
while(pTmpNode != NULL)
{
printf("%d ", pTmpNode->Data);
pTmpNode = pTmpNode->pNext;
}
printf("\n");
}
return;
}
3.查找
哈希表查找步骤:
- 用与插入一致的取模哈希函数,计算目标数据的桶索引,定位到对应链表;
- 从链表头开始遍历,若找到匹配数据,直接返回存在;
- 因链表升序,若遍历到比目标大的节点,直接返回不存在;
- 遍历完链表仍无匹配,返回不存在。
关键代码示例:
cpp
int FindHashTable(int TmpData)
{
int Index = 0;
Node_t *pTmpNode = NULL;
Index = TmpData % 10;
pTmpNode = pHashTable[Index];
while (pTmpNode != NULL)
{
if (pTmpNode->Data == TmpData)
{
return 1;
}
else if (pTmpNode->Data > TmpData)
{
return 0;
}
pTmpNode = pTmpNode->pNext;
}
return 0;
}
4.销毁
哈希表销毁步骤:
- 循环遍历哈希表的所有 10 个位置;
- 对每个位置的链表,用双指针遍历:先保存下一个节点地址,再释放当前节点;
- 释放完当前链表所有节点后,将头指针置 NULL,恢复初始空状态。
关键代码示例:
cpp
int DestroyHashTable(void)
{
int i = 0;
Node_t *pTmpNode = NULL;
Node_t *pFreeNode = NULL;
for (i = 0; i < 10; i++)
{
pTmpNode = pFreeNode = pHashTable[i];
while (pTmpNode != NULL)
{
pTmpNode = pTmpNode->pNext;
free(pFreeNode);
pFreeNode = pTmpNode;
}
pHashTable[i] = NULL;
}
return 0;
}