树
由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)
{
哈希冲突的解决方法
-
开放定址法(Open Addressing):当发生冲突时,按照某种规则在哈希表中寻找另一个空的位置来存放该元素。常见的开放定址法有线性探测法、二次探测法和双重哈希法等。
- 线性探测法:从发生冲突的位置开始,依次向后探测,直到找到一个空的位置为止。
- 二次探测法:在发生冲突时,以冲突位置为基础,依次加上或减去一个平方数,直到找到一个空的位置。
- 双重哈希法:使用两个哈希函数,当发生冲突时,使用第二个哈希函数计算的结果来调整探测的位置。
-
链地址法(Chaining):将哈希表中的每个位置看作一个链表的头节点,当发生冲突时,将元素存储在对应的链表中。这种方法可以有效地避免冲突的发生,但会增加存储开销。
-
再哈希法(Rehashing):准备多个哈希函数,当发生冲突时,使用另一个哈希函数重新计算位置,直到找到一个空的位置。
-
建立公共溢出区:将所有发生冲突的元素都存储在一个公共的溢出区中。
算法:
算法的设计
- 正确性,语法正确;合法的输入能得到合理的结果。对非法的输入,给出满足要求的规格说明对精心选择,甚至刁难的测试都能正常运行,结果正确
- 可读性,便于交流,阅读,理解 高内聚 低耦合
- 健壮性,输入非法数据,能进行相应的处理,而不是产生异常
- 高效率(时间复杂度)
- 低存储(空间复杂度)
算法时间复杂度
执行这个算法所花时间的度量
将数据量增长和时间增长用函数表示出来,这个函数就叫做时间复杂度。
一般用大O表示法:O(n)-----时间复杂度是关于数据n的一个函数。随着n的增加,时间复杂度增长较慢的算法时间复杂度低
时间复杂度的计算规则
- 用常数1 取代运行时间中的所有加法常数
- 在修改后的运行函数中,只保留最高阶项。
- 如果最高阶存在且系数不是1,则去除这个项相乘的常数。