树(tree)和哈希算法(Hash)

由n个节点组成的有限集。有一个根节点;其他节点只有一个前驱节点,但可以有多个后继节点。

  • 叶子节点(终端结点):只有前驱结点没有后继结点
  • 结点度:子节点的个数称之为度
  • 树的(广)度:树中各节点度的最大值 
  • 深度:从根节点到最底层节点的层数

森林:n个互不相交的树的集合

二叉树:任意一个节点的子节点个数不能超过2个(树的度为2),且子节点的位置不可更改。

满二叉树:在不增加树的层数的前提下,无法再增加一个节点的二叉树       

 满二叉树第K层有2^(k-1)个节点        K层满二叉树总共有2^k-1个节点  

完全二叉树:只是删除了满二叉树最底层最右边的连续若干个节点,形成了完全二叉树    

满二叉树 ==> 完全二叉树
完全二叉树 =/=> 满二叉树

二叉树的遍历:

(1)前序遍历:根    左    右

void pre_order(TNode_t *proot)
{
    if(NULL == proot)
    {
        return;
    }
    printf("%c", proot->data);
    pre_order(proot->pl);
    pre_order(proot->pr);
}

(2)中序遍历:左    根    右

void mid_order(TNode_t *proot)
{
    if(NULL == proot)
    {
        return;
    }
    mid_order(proot->pl);
    printf("%c", proot->data);
    mid_order(proot->pr);
}

(3)后续遍历:左    右    根

void pos_order(TNode_t *proot)
{
    if(NULL == proot)
    {
        return;
    }
    pos_order(proot->pl);
    pos_order(proot->pr);
    printf("%c", proot->data);
}

(4)层序遍历:逐层向下遍历

void layer_order(TNode_t *proot)
{
    QDataType outdata;
    Queue_t *pqueue = create_queue();
    if(NULL == pqueue)
    {
        printf("fail create_queue");
        return ;
    }

    push_queue(pqueue, proot);

    while(!is_empty_queue(pqueue))
    {
        pop_queue(pqueue, &outdata);
        printf("%c", outdata->data);
        if(outdata->pl != NULL)
        {
            push_queue(pqueue, outdata->pl);
        }
        if(outdata->pr != NULL)
        {
            push_queue(pqueue, outdata->pr);
        }
    }
    destroy_queue(pqueue);
}

二叉树的遍历特性:

  • 已知前序遍历序列和中序遍历序列,可以唯一确定一棵二叉树;
  • 已知后序遍历序列和中序遍历序列,可以唯一确定一棵二叉树;

哈希算法:

在记录的存储位置和它的关键字之间建立一种去特定的对应关系,使得每个关键字key对应一个存储位置;查找时,根据确定的对应关系,找到给定的key的映射。
    记录的存储位置 = f(关键字)我们把这种关系f称为哈希函数(散列函数);
    采用这种散列技术将记录存储在一块连续的存储空间,这块连续存储开空间称为哈希表或散列表。

  • 存储时,通过散列函数计算出记录的散列地址;
  • 查找时,根据同样的散列函数计算记录的散列地址,并按此散列地址访问记录。
//插入
int insert_hashtable(HSDataType data)
{
    int addr = hash_function(data.name[0]);

    HSNode_t *pnode = (HSNode_t *)malloc(sizeof(HSNode_t)); 
    if(NULL == pnode)
    {
        perror("fail malloc");
        return -1;
    }
    pnode->data = data;
    pnode->pnext = NULL;

    pnode->pnext = hashtable[addr];
    hashtable[addr] = pnode;
    
    return 0;
}

//遍历
void traverse_hashtable()
{
    for (int i = 0; i < HASH_SIZE; i++)
    {
        HSNode_t *pnode = hashtable[i];
        while (pnode)
        {
            printf("Name: %s, Tel: %s\n", pnode->data.name, pnode->data.tel);
            pnode = pnode->pnext;
        }
    }
}

//查找
HSNode_t *find_hashtable(char *name)
{
    int addr = hash_function(name[0]);
    HSNode_t *pnode = hashtable[addr];
    while(pnode)
    {
        if(!strcmp(pnode->data.name, name))
        {
            return pnode;
        }
        pnode = pnode->pnext;
    }
    return NULL;
}

//销毁
void destroy_hashtable()
{
    for(int i = 0; i < HASH_SIZE; i++)
    {
        HSNode_t *pnode = hashtable[i];
        while(pnode)
        {

哈希冲突的解决方法

  1. 开放定址法(Open Addressing):当发生冲突时,按照某种规则在哈希表中寻找另一个空的位置来存放该元素。常见的开放定址法有线性探测法、二次探测法和双重哈希法等。

    • 线性探测法:从发生冲突的位置开始,依次向后探测,直到找到一个空的位置为止。
    • 二次探测法:在发生冲突时,以冲突位置为基础,依次加上或减去一个平方数,直到找到一个空的位置。
    • 双重哈希法:使用两个哈希函数,当发生冲突时,使用第二个哈希函数计算的结果来调整探测的位置。
  2. 链地址法(Chaining):将哈希表中的每个位置看作一个链表的头节点,当发生冲突时,将元素存储在对应的链表中。这种方法可以有效地避免冲突的发生,但会增加存储开销。

  3. 再哈希法(Rehashing):准备多个哈希函数,当发生冲突时,使用另一个哈希函数重新计算位置,直到找到一个空的位置。

  4. 建立公共溢出区:将所有发生冲突的元素都存储在一个公共的溢出区中。

算法:

算法的设计

  1. 正确性,语法正确;合法的输入能得到合理的结果。对非法的输入,给出满足要求的规格说明对精心选择,甚至刁难的测试都能正常运行,结果正确
  2. 可读性,便于交流,阅读,理解    高内聚 低耦合
  3. 健壮性,输入非法数据,能进行相应的处理,而不是产生异常
  4. 高效率(时间复杂度)
  5. 低存储(空间复杂度)

算法时间复杂度

        执行这个算法所花时间的度量
        将数据量增长和时间增长用函数表示出来,这个函数就叫做时间复杂度。
        一般用大O表示法:O(n)-----时间复杂度是关于数据n的一个函数。随着n的增加,时间复杂度增长较慢的算法时间复杂度低

时间复杂度的计算规则

  1. 用常数1 取代运行时间中的所有加法常数
  2. 在修改后的运行函数中,只保留最高阶项
  3. 如果最高阶存在且系数不是1,则去除这个项相乘的常数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值