嵌入式养成计划-23-数据结构----直接插入排序、哈希查找,折半查找

五十九、部分的排序与查找

  • 回头找个时间单独搞一个排序查找的文章,这个就先水一水

59.1 直接插入排序

  • 在给出的序列中,假定一个已经排好序的序列,不断把后面的元素插入到该序列中,并排序
  • 思想: 从第二个元素开始,每个元素都逐个向前比较,如果找到它该待的地方(看升序还是降序,升序就是某个位置的前面比这个元素小,后面比这个元素大),那就将这个元素插入到这个位置上

59.1.1 直接插入排序的功能代码

//  指针指向数组的首地址,len代表数组的长度
void insert_sort(int *arr, int len)
{
    //  从第二个元素起,每个都要往前面进行插入排序
    for (int i = 1, j; i < len; i++)
    {
        //  暂存当前需要插入排序的元素
        int temp = arr[i];
        //  看前面有没有合适的位置,即那种左右一大一小的位置
        //  如果有,那需要挪动中间的这些元素
        //  j 记录挪到的那个位置
        for (j = i; j > 0 && arr[j - 1] > temp; j--)
        {
            //  从后向前逐个覆盖,挪动元素位置
            arr[j] = arr[j - 1];
        }
        //  挪完中间的元素后将需要进行插入的元素插到这个他该去的位置
        arr[j] = temp;
    }
}

59.1.2 完整代码示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//  指针指向数组的首地址,len代表数组的长度
void insert_sort(int *arr, int len)
{
    //  从第二个元素起,每个都要往前面进行插入排序
    for (int i = 1, j; i < len; i++)
    {
        //  暂存当前需要插入排序的元素
        int temp = arr[i];
        //  看前面有没有合适的位置,即那种左右一大一小的位置
        //  如果有,那需要挪动中间的这些元素
        //  j 记录挪到的那个位置
        for (j = i; j > 0 && arr[j - 1] > temp; j--)
        {
            //  从后向前逐个覆盖,挪动元素位置
            arr[j] = arr[j - 1];
        }
        //  挪完中间的元素后将需要进行插入的元素插到这个他该去的位置
        arr[j] = temp;
    }
}

int main(int argc, const char *argv[])
{
    int arr[] = {2, 3, 5, 4, 7, 5, 1, 2};
    int len = sizeof(arr) / sizeof(arr[0]);
    insert_sort(arr, len);
    
    for (size_t i = 0; i < len; i++)
    {
        printf("%d\n", arr[i]);
    }
    
    return 0;
}

59.2 哈希查找

  • 查找就是在一大堆东西里面找到想要的东西,应该不会有人不知道吧???
  • 不会吧?!
  • 不会吧?!

在这里插入图片描述

59.2.1 常用的处理冲突方法

  • 开放定址法
    • 线性探测法:往后逐个找空位,找到第一个空位坐下
    • 二次探测法:往后按加1/4/9/16/25/36…这种方式找,比如下一位,下第四位之类,这些数是1/2/3/4/5/6…的二次方
    • 伪随机探测法:百度去,字太多了,不想写
  • 再哈希法:再次取模,不过模的数应该会变,具体看需求
  • 链地址法:每个位置变成链表,相同位置的都在同一个链表里面
  • 建立公共溢出区:将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区

59.2.1 链地址法

在这里插入图片描述

59.2.2 哈希表的表长

  • 哈希表的长度:关键字的个数/0.75 ====>取最大素数
  • 关键字的个数 * 4/3

59.2.3 哈希表的结构体

//  关键字个数
#define LEN 10
//  hash表长度
#define MAX 13

typedef struct myhash
{
    //  存放关键字
    int key;
    //  指向下一个结点
    struct myhash *next;
} hash;

59.2.4 哈希表初始化

//  哈希表初始化
hash **hash_init()
{
    hash **h = (hash **)malloc(sizeof(hash *) * MAX);
    if (NULL == h)
    {
        printf("哈希表初始化失败\n");
        return NULL;
    }
    //  初始化哈希表的每个表元素
    for (int i = 0; i < MAX - 1; i++)
    {
        *(h + i) = NULL;
    }
    return h;
}

59.2.5 创建关键字结点

//  创建关键字结点
hash *creat_node(int value)
{
    hash *node = (hash *)malloc(sizeof(hash));
    if (NULL == node)
    {
        printf("关键字结点创建失败\n");
        return NULL;
    }
    //  初始化关键字结点
    node->key = value;
    node->next = NULL;
    return node;
}

59.2.6 向哈希表中存放关键字

//  向哈希表中存放关键字
void save_key(hash **h, int value)
{
    if (NULL == h)
    {
        printf("存关键字时入参为空\n");
        return;
    }
    //  通过取模计算该存到哈希表的那个区域
    int index = value % MAX;
    //  创建这个关键字的结点
    hash *node = creat_node(value);
    //  将这个结点连接到哈希表的这个区域里面
    //  此处采取的是头插法,方便,不需要再遍历到链表最后
    //  一开始哈希表的元素只是个空的指针,所以是直接 h[index]
    node->next = h[index];
    h[index] = node;
}

59.2.7 在哈希表中查找数据

//  在哈希表中查找数据
int search_hash(hash **h, int data)
{
    if (NULL == h)
    {
        printf("查找数据时入参为空\n");
        return -1;
    }
    //  用于记录第几个链表和链表中第几个元素
    int index = data % MAX, i = 1;
    //  指向要找的链表中第一个结点
    hash *node = h[index];
    //  如果当前结点是空的,啥都没有,那还找个锤子
    while (NULL != node)
    {
        //  找到了,赶紧输出,并且返回是哪个链表
        if (data == node->key)
        {
            printf("\n%d在哈希表的H[%d]链表中第%d个\n", data, index, i);
            return index;
        }
        //  尚未找到,位置计数变量+1
        ++i;
        //  继续向后找
        node = node->next;
    }
    //  找不到,提示并返回-1
    printf("找不到 %d 这个数据\n", data);
    return -1;
}

59.2.8 遍历输出哈希表

//  遍历输出哈希表
void out_hash(hash **h)
{
    if (NULL == h)
    {
        printf("遍历输出时入参为空\n");
        return;
    }
    //  从哈希表的第一段表元素开始,到最后一段表元素
    for (int i = 0; i < MAX; i++)
    {
        //  指向当前需要输出的表元素的第一个结点
        hash *p = h[i];
        printf("H[%-2d]:", i);
        //  循环输出这段表元素内所有的结点
        while (NULL != p)
        {
            //  输出当前结点的值
            printf("%d-->", p->key);
            //  在当前表元素内向后移动结点
            p = p->next;
        }
        puts("");
    }
}

59.2.9 销毁哈希表

//  销毁哈希表
void free_hash(hash ***h)
{
    if (NULL == h)
    {
        printf("销毁哈希表时入参为空\n");
        return;
    }
    //  与输出相同,都是需要每个遍历
    //  从哈希表的第一段表元素开始,到最后一段表元素
    for (int i = 0; i < MAX; i++)
    {
        puts("");
        //  指向当前需要释放的表元素内的第一个结点
        //  需要先解引用哦,可别再搞错咯
        hash *p = (*h)[i];
        //  循环释放这段表元素内所有的结点
        while (NULL != p)
        {
            //  指向当前准备释放的结点
            hash *q = p;
            //  在当前表元素内向后移动结点
            p = p->next;
            printf("%7d已被释放\n", q->key);
            //  释放当前结点
            free(q);
        }
        printf("H[%-2d]已被释放完\n", i);
    }
    free(*h);
    *h = NULL;
}

59.3 哈希表实现的完整代码(接口,功能实现,测试用主函数,Makefile)

接口(hash.h)

#ifndef __HASH_H__
#define __HASH_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//  关键字个数
#define LEN 10
//  hash表长度
#define MAX 13

typedef struct myhash
{
    //  存放关键字
    int key;
    //  指向下一个结点
    struct myhash *next;
} hash;

//  哈希表初始化
hash **hash_init();
//  创建关键字结点
hash *creat_node(int value);
//  向哈希表中存放关键字
void save_key(hash **h, int value);
//  在哈希表中查找数据
int search_hash(hash **h, int data);
//  遍历输出哈希表
void out_hash(hash **h);
//  销毁哈希表
void free_hash(hash ***h);
#endif

功能实现(hash.c)

#include "hash.h"

//  哈希表初始化
hash **hash_init()
{
    hash **h = (hash **)malloc(sizeof(hash *) * MAX);
    if (NULL == h)
    {
        printf("哈希表初始化失败\n");
        return NULL;
    }
    //  初始化哈希表的每个表元素
    for (int i = 0; i < MAX - 1; i++)
    {
        *(h + i) = NULL;
    }
    return h;
}

//  创建关键字结点
hash *creat_node(int value)
{
    hash *node = (hash *)malloc(sizeof(hash));
    if (NULL == node)
    {
        printf("关键字结点创建失败\n");
        return NULL;
    }
    //  初始化关键字结点
    node->key = value;
    node->next = NULL;
    return node;
}

//  向哈希表中存放关键字
void save_key(hash **h, int value)
{
    if (NULL == h)
    {
        printf("存关键字时入参为空\n");
        return;
    }
    //  通过取模计算该存到哈希表的那个区域
    int index = value % MAX;
    //  创建这个关键字的结点
    hash *node = creat_node(value);
    //  将这个结点连接到哈希表的这个区域里面
    //  此处采取的是头插法,方便,不需要再遍历到链表最后
    //  一开始哈希表的元素只是个空的指针,所以是直接 h[index]
    node->next = h[index];
    h[index] = node;
}

//  在哈希表中查找数据
int search_hash(hash **h, int data)
{
    if (NULL == h)
    {
        printf("查找数据时入参为空\n");
        return -1;
    }
    //  用于记录第几个链表和链表中第几个元素
    int index = data % MAX, i = 1;
    //  指向要找的链表中第一个结点
    hash *node = h[index];
    //  如果当前结点是空的,啥都没有,那还找个锤子
    while (NULL != node)
    {
        //  找到了,赶紧输出,并且返回是哪个链表
        if (data == node->key)
        {
            printf("\n%d在哈希表的H[%d]链表中第%d个\n", data, index, i);
            return index;
        }
        //  尚未找到,位置计数变量+1
        ++i;
        //  继续向后找
        node = node->next;
    }
    //  找不到,提示并返回-1
    printf("找不到 %d 这个数据\n", data);
    return -1;
}

//  遍历输出哈希表
void out_hash(hash **h)
{
    if (NULL == h)
    {
        printf("遍历输出时入参为空\n");
        return;
    }
    //  从哈希表的第一段表元素开始,到最后一段表元素
    for (int i = 0; i < MAX; i++)
    {
        //  指向当前需要输出的表元素的第一个结点
        hash *p = h[i];
        printf("H[%-2d]:", i);
        //  循环输出这段表元素内所有的结点
        while (NULL != p)
        {
            //  输出当前结点的值
            printf("%d-->", p->key);
            //  在当前表元素内向后移动结点
            p = p->next;
        }
        puts("");
    }
}

//  销毁哈希表
void free_hash(hash ***h)
{
    if (NULL == h)
    {
        printf("销毁哈希表时入参为空\n");
        return;
    }
    //  与输出相同,都是需要每个遍历
    //  从哈希表的第一段表元素开始,到最后一段表元素
    for (int i = 0; i < MAX; i++)
    {
        puts("");
        //  指向当前需要释放的表元素内的第一个结点
        //  需要先解引用哦,可别再搞错咯
        hash *p = (*h)[i];
        //  循环释放这段表元素内所有的结点
        while (NULL != p)
        {
            //  指向当前准备释放的结点
            hash *q = p;
            //  在当前表元素内向后移动结点
            p = p->next;
            printf("%7d已被释放\n", q->key);
            //  释放当前结点
            free(q);
        }
        printf("H[%-2d]已被释放完\n", i);
    }
    free(*h);
    *h = NULL;
}

测试用主函数(main.c)

#include "hash.h"

int main(int argc, char const *argv[])
{
    //  初始化哈希表
    hash **H = hash_init();

    int arr[LEN] = {25, 51, 8, 22, 26, 67, 11, 16, 54, 41};

    //  向哈希表中存放数据
    for (int i = 0; i < LEN; i++)
    {
        save_key(H, arr[i]);
    }

    //  输出哈希表
    out_hash(H);

    //  查找 67 这个数据
    int ret = search_hash(H, 67);
    printf("%d\n", ret);

    //  查找 66 这个数据
    int ret2 = search_hash(H, 66);
    printf("%d\n", ret2);

    //  销毁哈希表
    free_hash(&H);
    printf("\nH=%p\n", H);

    return 0;
}

makefile

EXE=hash
CC=gcc
CFLAGs=-c
OBJs+=main.o
OBJs+=hash.o
all=$(EXE)
$(EXE):$(OBJs)
	$(CC) $^ -o $@
%.o:%.c
	$(CC) $(CFLAGs) $^ -o $@
clean:
	rm *.o hash

59.4 折半查找

  • 折半查找,一定是对 有序序列 的查找
  • 每次查找都能将范围缩小一半
  • 从有序序列的中间开始,如果中间的值比待查找数据的值大,那就去低半边查,反之去高半边

59.4.1 折半查找的功能函数代码

//  value是要找的值
int half_search(int *a, int low, int high, int value)
{
    //  记录数组的中间位置
    int mid;
    //  在低边界和高边界还处于合理的情况进行循环
    while (low <= high)
    {
        //  更新序列的中间位置
        mid = (low + high) / 2;
        //  如果中间的这个位置是要找的
        //  表示找到了,并返回这个位置
        if (a[mid] == value)
        {
            printf("找到了\n");
            return mid;
        }
        //  如果这段序列的中间值比要找的大,那就找低半边
        else if (a[mid] > value)
        {
            //  更新序列的高边界
            high = mid - 1;
        }
        //  如果这段序列的中间值比要找的小,那就找高半边
        else
        {
            //  更新序列的低边界
            low = mid + 1;
        }
    }
    //  找完了,还是没找到
    printf("没找到\n");
    return -1;
}

59.4.2 完整代码示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int half_search(int *a, int low, int high, int value);

int main(int argc, const char *argv[])
{
    int arr[] = {1, 2, 3, 4, 6, 7, 8};
    int len = sizeof(arr) / sizeof(arr[0]);
    for (int i = len - 1; i >= 0; i--)
    {
        printf("%d\n", half_search(arr, 0, len - 1, arr[i]));
    }
    printf("%d\n", half_search(arr, 0, len - 1, 90));

    return 0;
}

//  value是要找的值
int half_search(int *a, int low, int high, int value)
{
    //  记录数组的中间位置
    int mid;
    //  在低边界和高边界还处于合理的情况进行循环
    while (low <= high)
    {
        //  更新序列的中间位置
        mid = (low + high) / 2;
        //  如果中间的这个位置是要找的
        //  表示找到了,并返回这个位置
        if (a[mid] == value)
        {
            printf("找到了\n");
            return mid;
        }
        //  如果这段序列的中间值比要找的大,那就找低半边
        else if (a[mid] > value)
        {
            //  更新序列的高边界
            high = mid - 1;
        }
        //  如果这段序列的中间值比要找的小,那就找高半边
        else
        {
            //  更新序列的低边界
            low = mid + 1;
        }
    }
    //  找完了,还是没找到
    printf("没找到\n");
    return -1;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhk___

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值