完全二叉树、非完全二叉树、哈希表的创建与遍历

一、完全二叉树

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;

}

三、哈希表

    1. 哈希:算法思想,将数据根据哈希算法映射成键值,根据键值来写入或是查找数据,以实现查找数据O(1)时间复杂度;
    1. 哈希碰撞(哈希冲突):多个数据通过哈希算法映射成同样的键值,说明产生哈希冲突;
    1. 链地址法:数据产生哈希冲突通过在同一键值位置用链表实现多个数据的存储;

示例:将0 - 100数据存放在哈希表中

头文件:

cpp 复制代码
typedef struct node
{
    int Data;
    struct node *pNext;
}Node_t;

1.创建

当不同数据通过哈希函数计算出相同索引 时,就会发生哈希冲突 ,代码中采用链地址法(开散列)解决,且对每个桶的链表做升序有序插入,既解决冲突,又让链表保持有序性。

(1)通过二级指针遍历实现,步骤为:

  1. 以哈希索引为起点,用二级指针ppTmpNode指向头指针(&pHashTable[Index]);
  2. 遍历链表:若当前节点非空待插入数据大于当前节点数据,继续向后遍历;
  3. 遍历结束时,ppTmpNode指向待插入位置的前驱指针 (可能是头指针,也可能是链表中某个节点的pNext)。

(2)确定插入位置后,完成新节点内存分配链表插入:

  1. malloc分配新节点内存,检查分配结果,失败则用perror打印错误信息并返回 - 1;
  2. 给新节点赋值:数据域存待插入数据,指针域指向当前待插入位置的节点(*ppTmpNode);
  3. 将前驱指针(*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.遍历(打印)

通过双层循环遍历哈希表的所有数据:

  1. 外层循环遍历哈希表的所有位置(0~9);
  2. 内层循环遍历每个位置对应的链表,依次打印节点数据;
  3. 索引: 数据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.查找

哈希表查找步骤:

  1. 用与插入一致的取模哈希函数,计算目标数据的桶索引,定位到对应链表;
  2. 从链表头开始遍历,若找到匹配数据,直接返回存在;
  3. 因链表升序,若遍历到比目标大的节点,直接返回不存在;
  4. 遍历完链表仍无匹配,返回不存在。

关键代码示例:

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.销毁

哈希表销毁步骤:

  1. 循环遍历哈希表的所有 10 个位置;
  2. 对每个位置的链表,用双指针遍历:先保存下一个节点地址,再释放当前节点;
  3. 释放完当前链表所有节点后,将头指针置 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;
}
相关推荐
霍理迪2 小时前
JS作用域与预解析
开发语言·前端·javascript
Traced back2 小时前
# C# WinForms 数据库清理系统基础知识与避坑指南
开发语言·数据库·c#
煜磊2 小时前
MD5加盐值-注册与登录
java·开发语言
you-_ling2 小时前
数据结构:5.哈希表
数据结构·散列表
茉莉玫瑰花茶2 小时前
C++ 17 详细特性解析(4)
开发语言·c++·算法
rosmis2 小时前
地铁病害检测系统软件改进记录-2-02
开发语言·前端·javascript
long3162 小时前
K‘ 未排序数组中的最小/最大元素 |期望线性时间
java·算法·排序算法·springboot·sorting algorithm
进击的小头2 小时前
FIR滤波器实战:音频信号降噪
c语言·python·算法·音视频
欧阳x天2 小时前
STL详解(九)—— stack和queue的模拟实现
开发语言·c++