数据结构5.(哈希表及数据的排序和查找算法)
1.哈希算法
将数据通过哈希算法映射成一个键值,存取都在同一位置实现数据的高效存储和查找,将时间复杂度尽可能降低至O(1),同样的参数返回同样的整数,不同的参数返回不同的整数
2. 哈希碰撞
多个数据通过哈希算法得到的键值相同,称为产生哈希碰撞。优秀的哈希算法会尽可能避免哈希碰撞。
3.哈希表(散列存储)
3.1哈希表的插入
int insert_hashtable(int tmpdata){int key = 0;linknode **pptmpnode = NULL;linknode *pnewnode = NULL;key = tmpdata % INDEX;for (pptmpnode = &phashtable[key]; *pptmpnode != NULL &&
(*pptmpnode)->data < tmpdata; pptmpnode = &(*pptmpnode)->pnext){} pnewnode = malloc(sizeof(linknode));if (NULL == pnewnode){perror("fail to malloc");return -1;}pnewnode->data = tmpdata;pnewnode->pnext = *pptmpnode;*pptmpnode = pnewnode;return 0;
}
3.2哈希表的遍历
int show_hashtable(void){int i = 0;linknode *ptmpnode = NULL;for (i = 0; i < INDEX; i++){printf("%d:", i);ptmpnode = phashtable[i];while (ptmpnode != NULL){printf("%2d ", ptmpnode->data);ptmpnode = ptmpnode->pnext;}printf("\n");}return 0;}
4.排序算法
4.1冒泡排序
排序方法: 相邻的两个元素比较,大的向后走,小的向前走,循环找len-1个大的值
int bubble_sort(int *parray, int len){int i = 0;int j = 0;int tmp = 0;for (j = 0; j < len-1; j++){for (i = 0; i < len-1-j; i++){if (parray[i] > parray[i+1]){ tmp = parray[i];parray[i] = parray[i+1];parray[i+1] = tmp;}}}return 0;}
4.2选择排序
排序方法: 从前到后找最小值与前面的元素交换 找到len-1个最小值,最后一个最大值即排序完成。
int select_sort(int *parray, int len){int i = 0;int j = 0;int tmp = 0;int min = 0;for (j = 0; j < len-1; j++){min = j;for (i = j+1; i < len; i++){if (parray[i] < parray[min]){min = i;}}if (min != j){tmp = parray[min];parray[min] = parray[j];parray[j] = tmp;}}return 0;}
4.3插入排序
比冒泡排序、选择排序更高效(实际移动次数更少),适用于小规模数据或近乎有序的数组(效率接近线性)。
原理:假设对数组 [5, 2, 4, 6, 1, 3]
进行升序排序:
初始已排序部分:
[5]
,未排序部分:[2, 4, 6, 1, 3]
。插入
2
:[2, 5]
(5
向后移动)。插入
4
:[2, 4, 5]
(5
向后移动)。插入
6
:直接放到末尾 →[2, 4, 5, 6]
。插入
1
:需移动到最前 →[1, 2, 4, 5, 6]
(所有元素后移)。插入
3
:放到2
和4
之间 →[1, 2, 3, 4, 5, 6]
。
int insert_sort(int *parray, int len){int tmp = 0;int i = 0;int j = 0;for (j = 1; j < len; j++){tmp = parray[j];for (i = j; i > 0 && tmp < parray[i-1]; i--){parray[i] = parray[i-1];}parray[i] = tmp;}return 0;}
4.4希尔排序
希尔排序是 插入排序的改进版,通过将数组分成多个子序列进行插入排序,逐步缩小子序列的间隔(step),最终使整个数组基本有序,最后进行一次标准的插入排序。
算法思想:
分组插入排序:选择一个递减的间隔序列(step),将数组分成若干子序列,对每个子序列进行插入排序。
逐步缩小间隔:随着step 的减小,子序列越来越接近整个数组,最终 step=1 时,相当于一次标准的插入排序,此时数组已经基本有序,排序效率高。
步骤:
选择一个初始间隔序列(如 step
= n/2,
step/= 2, ...
直到 step= 1
)。对每个 step,将数组分成 step 个子序列,每个子序列进行插入排序。
重复缩小 step,直到 step
= 1
,最后进行一次标准插入排序。
示例:
假设数组 [8, 3, 1, 2, 7, 5, 6, 4]
,初始 gap = 4
:
gap=4
:分成 4 个子序列[8,7], [3,5], [1,6], [2,4]
,分别排序 →[7,3,1,2,8,5,6,4]
。gap=2
:分成 2 个子序列[7,1,8,6], [3,2,5,4]
,分别排序 →[1,2,6,3,7,4,8,5]
。gap=1
:标准插入排序 →[1,2,3,4,5,6,7,8]
。
//希尔排序int shell_sort(int *parray, int len){int step = 0;int j = 0;int i = 0;int tmp = 0;for (step = len/2; step > 0; step /= 2){for (j = step; j < len; j++){tmp = parray[j];for (i = j; i >= step && tmp < parray[i-step]; i -= step){parray[i] = parray[i-step];}parray[i] = tmp;}}return 0;}
4.5快速排序
快速排序是一种 分治算法,通过选择一个键值,将数组分成两部分,左边小于键值,右边大于键值,然后递归排序左右两部分。
算法思想:
选择键值:通常选第一个、最后一个或随机元素。
分区:
将小于键值 的元素移到左边,大于键值的移到右边。
最终键值位于正确的位置。
递归排序:对左右子数组重复上述过程,直到子数组长度为 1。
步骤:
选择键值(如
arr[high]
)。初始化指针
i
(指向小于 键值 的最后一个元素)。遍历数组:
如果
arr[j] < 键值
,交换arr[i+1]
和arr[j]
,并i++
。
交换 pivot 到正确位置
i+1
。递归排序 左右子数组。
示例:
对数组 [5, 3, 8, 4, 2, 7, 1, 10]
进行排序(选择 5
作为 键值):
初始:
pivot = 5
[5, 3, 8, 4, 2, 7, 1, 10]↑i ↑j
i
向右找 >5
的元素(8
),j
向左找 <5
的元素(1
),交换 8
和 1。
[5, 3, 1, 4, 2, 7, 8, 10]↑i ↑j
i
继续右移找到 7
,j
左移找到 2
,交换:
[5, 3, 1, 4, 2, 7, 8, 10]↑j ↑i
此时 i > j
,循环终止,交换 pivot
和 arr[j]
[2, 3, 1, 4, 5, 7, 8, 10]
继续递归调用:左子数组 [2, 3, 1, 4]
(选 2
为 pivot),右子数组 [7, 8, 10]
(选 7
为 pivot)
1
5.查找算法
5.1折半查找(二分查找)
二分查找(折半查找)是一种高效的 有序数组查找算法,它通过 逐步缩小搜索范围 来快速定位目标值。其核心思想是 “分而治之”,每次比较后排除一半的无效数据。
核心思想
前提条件:数组必须是有序的(升序或降序)。
查找过程:
每次取数组的 中间元素 与目标值比较。如果中间元素等于目标值,直接返回位置。如果目标值 小于 中间元素,则在 左半部分 继续查找。如果目标值 大于 中间元素,则在 右半部分 继续查找。找到目标值或搜索范围为空(未找到)则终止。
示例
假设有序数组 [1, 3, 5, 7, 9, 11]
,查找 target = 7
:
初始范围:
low = 0
,high = 5
。mid = 2
,arr[2] = 5
<7
→ 搜索右半部分,low = 3
。
新范围:
[7, 9, 11]
(low = 3
,high = 5
)。mid = 4
,arr[4] = 9
>7
→ 搜索左半部分,high = 3
。
最终比较:
low = 3
,high = 3
。mid = 3
,arr[3] = 7
==target
→ 返回3