IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【C语言】如何“查找”到你想要数字(二分查找,哈希表)——LeetCode刷题分享 -> 正文阅读

[数据结构与算法]【C语言】如何“查找”到你想要数字(二分查找,哈希表)——LeetCode刷题分享

目录

一、 介绍

?二、顺序表查找

2.1 认识与基础

2.2 优化改进

2.3 例题:移除元素?

三、有序表查找

3.1 二分查找

3.2 *插值查找

四、散列表查找(哈希表)

4.1 初步了解

4.2 哈希表的运用

(1)初阶

(2)中阶

(3)高阶?

End


一、 介绍

相信大家再日常学习生活中遇到难题常常会上网输入关键字,查找到你想要的答案;那么在C语言中如何查找到你想要的数据呢?初学C语言,老师肯定会让你试试在数组中找到指定的一个数,将他改成另一值;查找的方法多种多样,运用场景也千变万化,接下来我们一起来学习基础且实用的查找方法

?二、顺序表查找

2.1 认识与基础

若给定一个数组,在数组中查找某一元素,并返回其下标。就像在衣柜里找衣服一样,按顺序一件一件的查找到你想要的就行。我们看看代码是如何实现的??

int Sequential_Sreach(int* a, int key, int n)  //a为传入的数组;key为需要查找的元素;
{                                              //n为数组元素个数;
	for (int i = 0; i < n; i++)
	{
		if (a[i] == key)
			return i;
	}
	return 0;         //若没有找到,则返回0;
}

2.2 优化改进

上述方法,每进入一次循环都要判断 i 是否越界,接下来介绍一个改进后的代码:若要查找数组下标从1到n中的某个元素,设置一个哨兵,每次与该哨兵比较,一起通过代码来理解??

int Sequential_Sreach(int* a, int key, int n)
{
	a[0] = key;
	int i = n;
	while (a[i] != key)
	{
		i--;
	}
	return i;              
}

与之前不一样的是这里是找下标1到n中的元素,将a[0]赋值需要查找的数,从右往左查找,进入while循环,减去下标 i 直到a[ i ] ==key;

若当数组中没有查到到,此时i = 0,跳出while循环,return 0;

2.3 例题:移除元素?

?LeetCode? 27.移除元素https://leetcode.cn/problems/remove-element/

给你一个数组 nums?和一个值 val,你需要原地移除所有数值等于?val?的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

?审题:最开始做这道题的时候可能会轻敌,移除元素不就只需要将查找到的元素删除吗,但这是个数组,并且题目要求返回删除后的长度,所以每次删除一个元素,需要将其后面的所有元素往前移一位,像这样你可以很简单的将其暴力地解出,但是效率会很低;这里我们介绍一个做题时常用且巧妙地方法:双指针(左右指针)

解题:

(1)定义 left , right 指向同一数组,需要查找的元素? val

(2)※for循环中,right遍历整个数组,若right指向的元素等于 val,则left保持原来位置(该位置值为val,待覆值),right指向下一位置;

? ? ? ?当right指向的元素不为val时,将此时的值赋给left指向的值,就将原本为val的值覆盖了

(3)※??left为数组下标,最后left指向的是left+1个数,但移除后的的个数为left;?

int removeElement(int* nums, int numsSize, int val){
    int left = 0;
    for(int right = 0; right<numsSize;right++)
    {
        if(nums[right] != val)
        {
            nums[left] = nums[right];
            left++;
        }
    }
    return left;
}

?图解:

三、有序表查找

?如果说顺序表是从一堆衣服中找到你想要的,那么有序表就是从从小到大的套娃中找到想要的大小;🤔

3.1 二分查找

?折半查找又称折半查找,顾名思义就是将一个有序的数组(升序)一中间数为“折痕”,将其折为两部分,左部分数小,右部分数大,联想二叉树,也是类似的二分法;

查找:

若其大于中间数(mid),则在右部分(反之,则在左部分);

之后再将mid+1作为右部分的左边界(mid-1作为左部分的右边界),再一次找出该部分的中间值;

重复以上步骤,直至 mid 为要查找的值??

int Binary_Sreach(int* a, int key, int n )
{
	int left = 0;
	int right = n - 1;
	int mid;
	while (left <= right)
	{
		mid = (left + right) / 2;   //  [left,mid-1] [mid] [mid+1,right]
		if (mid > key)
			right = mid-1;

		else if (mid < key)
			left = mid+1;

		else
			return mid;

	}
	return 0;
}

?时间复杂度:O(logn)

3.2 *插值查找

插值查找算是折半查找的优化,折半查找中mid=(left + right)/ 2,即

mid = left+(1/2)*(right - left)

插值查找就是将上方公式中的1/2改成(key-a[left])/(a[right] - a[left]),则:

mid = left +?(key - a[left])/(a[right] - a[left])

这样之后查找的效率提升了,但是二叉查找还是相对稳定一些;

四、散列表查找(哈希表)

4.1 初步了解

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?参考文献:《大话数据结构》

设想你进入图书馆,如果你是社恐,肯定会像顺序表查找一样,在一个个书架里查找你想要的书,这样效率肯定会很差;但如果你善于社交,向图书管理员询问这本书的名字(key),那么他肯定会迅速的锁定这本书的地址,最后将书找出来给你。

就像询问图书管理员一样,我们通过一个函数,该函数满足:

储存位置 = f(key)

又因为自变量key与因变量是一一对应的,所以每输入一个key的值,就会对应一个储存位置;

※我们把这个对应关系 f 称为散列函数,又称为哈希(Hash) 函数。按这个思想,将所有函数值及储存位置记录在一块连续的储存空间中,将其称为哈希表或是散列表,

※那么关键字(key)对应的记录存储位置我们称为散列地址

4.2 哈希表的运用

通过以上的了解,我们可以发现,哈希表可以用来储存也可以用来查找 ,在做题中大部分使用来储存的;废话不多说,我们用例题来深入的解析??

(1)初阶

LeetCode 1748.唯一元素的和https://leetcode.cn/problems/sum-of-unique-elements/submissions/

给你一个整数数组?nums?。数组中唯一元素是那些只出现?恰好一次?的元素。请你返回?nums?中唯一元素的??。

示例 1:

输入:nums = [1,2,3,2]
输出:4
解释:唯一元素为 [1,3] ,和为 4 。

思路分析:

? 这一题是一个非常明显的用哈希表计数的题,数组中每个元素作为key,用其指向哈希表中的一个储存空间;

? 简单的来说,就是用nums中的数作为哈希表的下标,遍历一遍nums,对每一个数对应指向哈希表的位置+1,则nums中恰好出现一次的数指向哈希表中的数为1,最后遍历哈希表中的数,判断其是否为1就行了

代码如下??

int sumOfUnique(int* nums, int numsSize) {
	int hash[101];
	memset(hash, 0, sizeof(hash));   
        //创建哈希表,因为nums数组的数范围为1-100;所以哈希表中需要啊有101个空间
	
    for (int i = 0; i < numsSize; i++)
	{
		hash[nums[i]]++;          //nums[i]的值作为哈希表的下标
	}
    int sum = 0;
	for (int j = 1; j <= 100; j++)
	{
		if (hash[j] == 1)
			sum += j;              //由上可知,j为nums数组的值,所以直接加在sum中就行了

	}
	return sum;
}

个人总结:

以我自己来看,哈希表就相当于我们数学里学的复合函数??g?( f?( x ) ) ,重点在于内函数的值作为外函数的变量。

接下来我们再通过一题来进一步熟练哈希表:

(2)中阶

??????2206. 将数组划分成相等数对 - 力扣(LeetCode)

给你一个整数数组?nums?,它包含?2 * n?个整数。

你需要将?nums 划分成?n?个数对,满足:

每个元素 只属于一个 数对。
同一数对中的元素 相等?。
如果可以将 nums?划分成 n?个数对,返回 true?,否则返回 false?。

示例 1:

输入:nums = [3,2,3,2,2,2]
输出:true
解释
nums?中总共有 6 个元素,所以它们应该被划分成 6 / 2 = 3 个数对。
nums 可以划分成 (2, 2) ,(3, 3) 和 (2, 2)?

思路分析:

? 由题可知,每个数对有两个数组成且这两个数相等,所以再nums数组中,同一元素的个数一定是偶数;

? 所以用哈希表来记录同一元素的个数,最后判断其是否为偶数就行了

代码如下??

bool divideArray(int* nums, int numsSize){
    int hash[501];
    memset(hash,0,sizeof(hash));
    for(int i = 0;i<numsSize;i++)
    {
        hash[nums[i]]++;        //记录某一元素的个数,该元素为哈希表的下标
    }
    for(int j = 0;j<numsSize;j++)
    {
        if(hash[nums[j]] % 2 != 0)
        return false;               //若nums中有一个元素的个数不为偶数,则返回假

    }
    return true;      //若哈希表中所有元素均为偶数,则返回真
}

(3)高阶?

?

817. 链表组件

给定链表头结点?head,该链表上的每个结点都有一个 唯一的整型值 。同时给定列表?nums,该列表是上述链表中整型值的一个子集

返回列表?nums?中组件的个数,这里对组件的定义为:链表中一段最长连续结点的值(该值必须在列表?nums?中)构成的集合。

示例 :

?

输入: head = [0,1,2,3,4], nums = [0,3,1,4]
输出: 2
解释: 链表中,0 和 1 是相连接的3 和 4 是相连接的,所以 [0, 1] 和 [3, 4] 是两个组件,故返回 2。

思路分析:

??首先这道题读题读懂是非常重要的,通过题我们可以知道,这道题简单的来说就是将数组对应链表,能将数组分成多少段子链表;

? 像这种有映射关系的题我们就可以试着用哈希表来解决:

解题步骤:

? 创建哈希表,首先要知道哈希表的空间,因为数组是链表中整型值的子集,哈希表的空间不能直接是numsSize,要用数组里的值作为哈希表的下标,再用链表中的值找到其下标对应的值,所以哈希表的空间大小是链表的长度

int LisrSize(struct ListNode* head)
{
	struct ListNode* cur = head;
	int size = 0;
	while (cur != NULL)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

? 所以说第一步是求出链表长度??,然后创建哈希表;

? 之后遍历数组,对于数组中出现的数,我们将它作为哈希表的下标位置,对该位置的值+1;

? 最后一步,遍历链表,判断链表中的整型值对应哈希表的位置是0还是1,0即为断开之处,1即为链接之处。加上计数,这道题就完成了。👌

我们看看代码如何实现??

int numComponents(struct ListNode* head, int* nums, int numsSize) {
	int Lsize = LisrSize(head);   //求链表长度
	int hash[Lsize]; 
	memset(hash, 0, sizeof(hash));     //创建哈希表
	for (int i = 0; i < numsSize; i++)
	{
		hash[nums[i]]++;
	}
	int ret = 0;           
	while (head != NULL)
	{
		if (head != NULL && hash[head->val] == 1)   //出现1,则计数一次
			ret++;       
		while (head != NULL && hash[head->val] == 1)
		{
			head = head->next;           //将这一小段走完,走到断开处0
		}
        if (head != NULL )         //  ※每次head->next都需判断是否指向NULL
        head = head->next;         //走到断开处0之后,再去寻找下一个开端1
	}
	return ret;
}

?

End

? 查找的讲解到这里就暂时结束了,这是一篇偏新手向的博客(因为我就是新手🤣)?,熟练的运用需要大量的练习,让我们在题海相见吧!

? 本篇的重点当然是哈希表了,虽然这里讲解的很“肤浅”,但足已解决一些简单的哈希表运用题?👌

? 如果之后本人学有更“先进”的算法知识会立刻写博客分享给大家😈;

? 感谢阅读? Thanks for your reading??

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-10-17 12:59:49  更:2022-10-17 13:01:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 2:51:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计