算法基础(1):排序和查找算法

news2024/12/28 2:11:28

1、排序算法

在这里插入图片描述

1.1、堆排序(大顶堆)-重点:

  • 参考文章:堆排序1、堆排序二

  • 前置知识:

    • 大顶堆:完全二叉树,且父节点大于左右儿子,左右子树又是大顶堆,依赖数组来实现(vector)
    • 第一个节点的父节点:(i-1)/2,第i个节点的左儿子:i*2+1,第i个节点的右儿子:i*2+2,这里i从0开始;
    • 最后有儿子的节点:数组元素有n个,则最后一个有儿子的节点(n-1-1)/2=n/2-1
  • 堆排序基本思想:分为建堆排序两步,而核心步骤是对堆的属性进行维护

    • 维护堆:维护第i个节点的堆属性, 将该节点和左右儿子节点进行对比,选择大的进行交换,然后向下遍历,直到全部都满足堆的属性
    • 建堆:对已有的数组,从最后一个有儿子的节点开始向上遍历建立大顶堆,每次都需要维护节点的堆属性
    • 排序:每次将第一个元素和最后一个元素进行交换,每次交换,需要维护的数组长度-1,维护的节点是第一个节点
  • 复杂度:

    • 时间复杂度:最坏、最好、平均时间复杂度均为O(nlogn)
    • 空间复杂度O(1)
    • 描述是个不稳定的内部排序算法
  • 代码实现:实列测试

    class Solution {
    public:
        void heapify(vector<int>& nums,int n,int i) //维护第i个节点的堆结构
        {
            int largest = i;
            int left = i*2+1;
            //最多遍历树的高度,时间复杂度O(log n)
            while(left<n) //当left是最后一个节点,也还需要判断一次
            {
                if(left<n&&nums[largest]<nums[left]) largest = left; 
                if(left+1<n&&nums[largest]<nums[left+1]) largest = left+1; //
                if(largest==i) break; //当没有子节点的时候,或者不需要维护的时候跳出while
                swap(nums[largest],nums[i]); //将大的值放到i的位置上
                i = largest; //向下循环子节点
                left = i*2+1; //子节点的左儿子
            }
        }
        void heapSort(vector<int>& nums,int n)
        {
            //建堆时间复杂度O(nlog n),从第一个有子节点的节点开始维护
            for(int i =n/2-1;i>=0;i--)         {
                heapify(nums,n,i); 
            }
            for(int i=n-1;i>=0;i--) //堆排序 时间复杂度O(nlogn)
            { //每次将最后一个节点和第一个节点互换
                swap(nums[i],nums[0]);
                heapify(nums,i,0); //进行枝剪维护第0个节点
            }
        }
        vector<int> sortArray(vector<int>& nums) {
            heapSort(nums,nums.size());
            return nums;
        }
    };
    

1.2、快速排序-重点

  • 快速排序基本思想:在区间中,每次选择一个基点,小于基点的放在基点左边,大于基点的放在右边,分别对两边进行快速排序
  • 复杂度:
    • 时间复杂度:最好、平均时间复杂度为O(nlogn),最坏时间复杂度O(n^2)-序列基本有序,递归O(n)
    • 空间复杂度: 递归深度 O(log n)
    • 描述是个不稳定的内部排序
  • 代码实现:参考视频
    • 二路快排,普通快排

      class Solution {
      public:
          int partition(vector<int>& nums,int l,int r)
          {
              int pivot = rand()%(r-l+1)+l;
              swap(nums[pivot],nums[r]);
              int i=l;
              for(int j=l;j<=r-1;j++) //用j遍历数组,
              {
                  if(nums[j]<nums[r])
                  {
                      swap(nums[j],nums[i]);  //指针i始终指向pivot右边的第一个元素
                      i++; //i前面的都是小于pivot的元素,
                  }
              }
              //遍历完了,i依旧指向大于pivot的元素的第一位
              // j指向r的位置,也就是pivot的位置,只需要将pivot的位置和i的位置进行交换即可
              swap(nums[r],nums[i]);
              return i;
          }
          void quikSort(vector<int>& nums,int left,int right)
          {
              if(left>=right) return;
              int mid = partition(nums,left,right);
              quikSort(nums,left,mid-1);
              quikSort(nums,mid+1,right);
          }
          vector<int> sortArray(vector<int>& nums) {
              quikSort(nums,0,nums.size()-1);
              return nums;
          }
      };
      
    • 三路快排,用于重复元素多的情况

      class Solution {
      public:
          vector<int> partition(vector<int>& nums,int l,int r)
          {
              int pivot = rand()%(r-l+1)+l;
              swap(nums[pivot],nums[r]);
              // 三路快排,p指向pivot元素相同的第一个元素
              // i指向大于pivot的元素的第一个位置
              int i=l;
              int p=l;
              for(int j=l;j<=r-1;j++)
              {
                  if(nums[j]<nums[r])
                  {
                      swap(nums[j],nums[i]);
                      swap(nums[i],nums[p]);
                      i++; 
                      p++;
                  }
                  else if(nums[j]==nums[r])
                  {
                      swap(nums[j],nums[i]);
                      i++;
                  }
              }
              
              swap(nums[r],nums[i]);
              return vector<int>{p,i};
          }
          void quikSort(vector<int>& nums,int left,int right)
          {
              if(left>=right) return;
              vector<int> v = partition(nums,left,right);
              quikSort(nums,left,v[0]-1);
              quikSort(nums,v[1]+1,right);
          }
          vector<int> sortArray(vector<int>& nums) {
              quikSort(nums,0,nums.size()-1);
              return nums;
          }
      };
      

1.3、归并排序-重点

  • 归并排序基本思想:分治的思想,每将数组递归分成长度接近的两个部分,再进行排序合并回溯
  • 复杂度
    • 时间复杂度:平均、最好、最坏均为O(nlogn)
    • 空间复杂度:需要引入辅助空间O(n),如果是给链表排序则只需要O(1)
    • 描述:是个稳定的外部排序算法
  • 代码实现:
    • 数组归并
    class Solution {
    public:
        void merge(vector<int>&nums,vector<int>& arr,int left,int mid,int right)
        {
           int l = left;//左边第一个未排序元素
           int r = mid+1;//右边第一个未排序元素
           int pos = left;//arr数组
           // 合并
            while(l<=mid&&r<=right)
            {
                if(nums[l]<nums[r]) arr[pos++] = nums[l++];
                else arr[pos++] = nums[r++];
            }
           //合并剩余元素
            while(l<=mid) arr[pos++] = nums[l++];
            while(r<=right) arr[pos++] = nums[r++];
           //将临时数组进行拷贝
           while(left<=right)
           {
               nums[left] = arr[left];
                left++;
           }
        }
        void mergeSort(vector<int>& nums,vector<int>& arr,int left,int right)
        { 
           if(left>=right) return;
           int mid = (right+left)/2;
           mergeSort(nums,arr,left,mid);
           mergeSort(nums,arr,mid+1,right);
           merge(nums,arr,left,mid,right);
        }
        vector<int> sortArray(vector<int>& nums) {
            vector<int> arr(nums.size());
            mergeSort(nums,arr,0,nums.size()-1);
            return nums;
        }
    };
    
    • 链表归并:

1.4、冒泡排序

  • 冒泡排序基本思想:每次循环将最大元素交换到最右边,每次内部循环都需要交换

  • 和选择排序的区别:异曲同工,选择排序交换次数少,但是在数组有序的情况下,依旧需要完整的遍历完,时间复杂度稳定到O(n^2),冒泡排序,可以在数组有序的情况下提前结束

  • 复杂度:

    • 时间复杂度:平均时间复杂度O(n^2),最好时间复杂度O(n),平均时间复杂度O(n^2) - 空间复杂度:O(1)
    • 描述:是个稳定的内部排序
  • 代码实现:

    // 简单版本,和选择排序基本一致
    class Solution {
    public:
        void bubbleSort(vector<int>& nums,int n)
        {
            
            for(int i=nums.size()-1;i>=0;i--)
            {
                for(int j=0;j<i;++j)
                {
                    if(nums[j]>nums[j+1]) swap(nums[j],nums[j+1]);
                }
            }
        }
        vector<int> sortArray(vector<int>& nums) {
            bubbleSort(nums,nums.size());
            return nums;
        }
    };
    // 优化版本
    	class Solution {
    	public:
    	    void bubbleSort(vector<int>& nums,int n)
    	    {
    	        bool flag = false;
    	        for(int i=nums.size()-1;i>=0;i--)
    	        {
    	            flag = false;
    	            for(int j=0;j<i;++j) //一般过去是有序的,则时间复杂度只有O(n)
    	            {
    	                if(nums[j]>nums[j+1])
    	                {
    	                    swap(nums[j],nums[j+1]);
    	                    flag = true;
    	                }
    	            }
    	            if(!flag) break;
    	           
    	        }
    	    }
    	    vector<int> sortArray(vector<int>& nums) {
    	        bubbleSort(nums,nums.size());
    	        return nums;
    	    }
    	};
    

1.5、选择排序

  • 选择排序基本思想:每次选择一个最大的元素排到后面,每次内部循环只需要交换一次

  • 复杂度:

    • 时间复杂度:最好、最坏、平均时间复杂度为O(n^2)
    • 空间复杂度:没有辅助容器为O(1)
    • 描述:是个不稳定的内部排序
  • 代码实现:

    class Solution {
    public:
        void selectSort(vector<int>& nums,int n)
        {
            int Max=-1;
            for(int i=nums.size()-1;i>=0;i--)
            {
                Max = i;
                for(int j=0;j<i;++j)
                {
                    if(nums[j]>nums[Max]) Max=j;
                }
                swap(nums[i],nums[Max]);
            }
        }
        vector<int> sortArray(vector<int>& nums) {
            selectSort(nums,nums.size());
            return nums;
        }
    };
    

1.6、插入排序

  • 插入排序基本思想:每次将一个元素插入到前面维护好顺序的元素里面

  • 复杂度:

    • 时间复杂度:最坏、平均为O(n^2),最好为O(n)-基本有序的情况
    • 空间复杂度:O(1)
    • 描述:是个稳定的内部排序算法
  • 代码实现:

    class Solution {
    public:
        void InsertSort(vector<int>& nums,int n)
        {
            for(int i=1;i<n;++i)
            {
            	// 每次将第i个元素插入到前i-1数组中
                int tmp = nums[i]; 
                int j=i;
                while(j>0 && nums[j-1]>tmp)
                {
                    nums[j] = nums[j-1];
                    --j;
                }
                nums[j] = tmp;
            }
        }
        vector<int> sortArray(vector<int>& nums) {
            InsertSort(nums,nums.size());
            return nums;
        }
    };
    

2、查找算法

2.1、二分查找

  • 基本思想:前提是数组有序,通过中间点折半的思想实现快速的查找

  • 红蓝边界思想:

    int left = -1,right = N; // 数组下标从0到N-1
    while left+1!=right
    	m = (left+right)/2;
    	if IsBlue(m)
    		left = m;
    	else 
    		right = m;
    return left or right;  //返回值根据题目要求来定
    
  • 复杂度:

    • 时间复杂度:O(log n)
  • 代码实现:

    • 在有序数组中查找元素的下标:测试实列

      class Solution {
      public:
          /*红蓝边界求值,时间复杂度O(log n)
          1.没有这个元素的话,且right进行了移动
          2.没有这个元素的话,且right没有进行移动
          3.有这个元素,直接返回right
          */
          int search(vector<int>& nums, int target) {
              int left = -1;
              int right = nums.size();
              while(left+1!=right)
              {
                  int mid = (left+right)/2;
                  if(nums[mid]<target) left = mid;
                  else right = mid;
              }
              //需要判断对应的节点不存在的可能
              if(right==nums.size()||nums[right]!=target) return -1; 
              else return right;
          }
      };
      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/910601.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

接口性能测试 —— Jmeter并发与持续性压测

接口压测的方式&#xff1a; 1、同时并发&#xff1a;设置线程组、执行时间、循环次数&#xff0c;这种方式可以控制接口请求的次数 2、持续压测&#xff1a;设置线程组、循环次数&#xff0c;勾选“永远”&#xff0c;调度器&#xff08;持续时间&#xff09;&#xff0c;这种…

Git使用篇:MacWindow---Vscode 终端命令行显示分支名和Tab自动补全

###&#xff1a;mac终端美化 https://www.jianshu.com/p/fd457aaee3e7 配置地址 终端改成git // 输入命令,检查是否有/bin/zsh(macOS自带zsh) cat /etc/shells // 修改默认的bash为zsh,重启Terminal chsh -s /bin/zsh// 检查修改结果,显示/bin/zsh即成功. echo $SHELLwindow终…

8年经验之谈 —— 基于jmeter的性能全流程测试

01、做性能测试的步骤 1、服务器性能监控 首先要在对应服务器上面安装性能监控工具&#xff0c;比如linux系统下的服务器&#xff0c;可以选择nmon或者其他的监控工具&#xff0c;然后在jmeter模拟场景跑脚本的时候&#xff0c;同时启动监控工具&#xff0c;这样就可以获得jm…

ChatGLM-6B微调记录

目录 GLM-130B和ChatGLM-6BChatGLM-6B直接部署基于PEFT的LoRA微调ChatGLM-6B GLM-130B和ChatGLM-6B 对于三类主要预训练框架&#xff1a; autoregressive&#xff08;无条件生成&#xff09;&#xff0c;GPT的训练目标是从左到右的文本生成。autoencoding&#xff08;语言理解…

每日两题 83删除排序链表的重复元素 82删除排序链表的重复元素||

83 题目 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,1,2] 输出&#xff1a;[1,2]示例 2&#xff1a; 输入&#xff1a;head [1,1,2,3,3] 输出&…

*看门狗各函数讲解

独立看门狗配置步骤 1&#xff09;取消寄存器写保护&#xff0c;通过函数 HAL_IWDG_Init 实现。 看门狗的喂狗时间&#xff08;也就是看门狗溢 出时间&#xff09;的计算方式为&#xff1a; Tout((42^prer) rlr) /32 其中 Tout 为看门狗溢出时间&#xff08;单位为 ms&#…

Navicat里.sql文件转换到.db文件

1.在桌面创建一个xxx.db文件&#xff0c;在navicat中创建数据库的时候会用到 2.在navicat创建数据库 在 Navicat 的导航栏中&#xff0c;选择 "工具" -> "SQL 文件执行器"。 在 SQL 文件执行器中&#xff0c;单击 "打开" 按钮&#xff0c;选择…

无锡市惠丰电子有限公司采购ZJ-5型叠层压电D33测试仪及相关配套夹具

无锡市惠丰电子有限公司采购ZJ-5型叠层压电D33测试仪及相关配套夹具 无锡市惠丰电子有限公司成立于1998年&#xff0c;是一家专门从事电子陶瓷产品研发&#xff0c;生产&#xff0c;销售&#xff0c;服务于一体的股份制公司&#xff0c;座落于素有鱼米之乡的太湖之滨城市——无…

Wapp群发王,释放全球市场潜力

随着移动互联网的普及&#xff0c;WhatsApp在全球范围内的用户数量持续迅猛增长。据数据显示&#xff0c;截至2022年2月&#xff0c;WhatsApp的月活跃用户数已超过20亿&#xff0c;遍布全球180多个国家和地区&#xff0c;在 58 个国家更成为常用通讯软件。这使得 WhatsApp 成为…

滑块验证3-接第1篇

driver拖动滑块 滑块验证的过程比较常使用driver模拟滑动&#xff0c;这样能够省去很多验证操作。 如果设置适合的滑动轨迹&#xff0c;成功率是非常高的。 当然&#xff0c;麻烦的是现在很多站点都做了识别driver的反爬&#xff0c;而且比较受网络的影响。 所需包 seleniu…

k8s service (二)

K8s service (二) Endpoint Endpoint是kubernetes中的一个资源对象&#xff0c;存储在etcd中&#xff0c;用来记录一个service对应的所有pod访问地址&#xff0c;它是根据service匹配文件中selector描述产生的。 一个Service由一组Pod组成&#xff0c;这些Pod通过Endpoints…

特斯拉Model 3的七年狂飙

‍ 作者 | 张祥威 编辑 | 德新 发布一周拿下32万张订单&#xff0c;之后用时五年&#xff0c;交付量突破100万辆。粗略计算&#xff0c;自2016年发布至今&#xff0c;特斯拉Model 3已交付超150万辆。 放眼新能源赛道&#xff0c;如此战绩 别无二家。 Model 3踩中纯电动车的…

LeetCodeHot100:Python 版本之贪心

121. 买卖股票的最佳时机 55. 跳跃游戏 那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点&#xff01;(看覆盖范围) 每次移动取最大跳跃步数&#xff08;得到最大的覆盖范围&#xff09;&#xff0c;每移动一个单位&#xff0c;就更新最大覆盖范围。 贪心算法局部最…

ClickHouse(二十四):Flink 写入 ClickHouse API

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术&#xff0c;IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &…

光伏发电+boost+储能+双向dcdc+并网逆变器控制(低压用户型电能路由器仿真模型)【含个人笔记+建模参考】

MATALB代码链接&#xff1a;光伏发电boost十储能十双向dcdc十并网逆变器 个人笔记与建模参考请私信发送 包含Boost、Buck-boost双向DCDC、并网逆变器三大控制部分 boost电路应用mppt&#xff0c; 采用扰动观察法实现光能最大功率点跟踪 电流环的逆变器控制策略 双向dcdc储能系…

酷开会员 | 酷开系统给孩子更好的选择

暑假到来&#xff0c;很多家长对孩子看电视的行为感到无力&#xff1a;孩子放假在家一天到晚就对着电视&#xff0c;作业不拖到最后一刻绝不写&#xff01; 孩子早上醒来就吵着看电视&#xff0c;一看就收不住&#xff0c;不吃不喝的。家长则每天都在和孩子斗智斗勇&#xff0…

Pandas学习(完成文件写入、追加写入、读取操作)

问题引入 现在有这么一个需求 我要对我的很多设备进行快照处理&#xff0c;打完快照之后需要记录我的设备IP和快照时间 当我们解决了需求的其他内容&#xff0c;只剩记录信息的时候&#xff0c;可以怎么做呢 这时候就可以引入我们的pandas模块啦&#xff0c;它对数据进行一系列…

Docker常用操作命令(一)

Docker常用操作命令 1、搜索镜像 docker search命令搜索存放在 Docker Hub中的镜像,此命令默认Docker会在Docker Hub中搜索镜像&#xff0c;可以配置了其他镜像仓库 [rootzch01 ~]# docker search centos NAME:镜像仓库名称DESCRIPTION:镜像仓库描述STARS&#xff1a;镜像仓…

spring cloud 之 dubbo nacos整合

整体思路&#xff1a; 搭建本地nacos服务&#xff0c;详见docker安装nacos_xgjj68163的博客-CSDN博客 共三个工程&#xff0c;生产者服务、消费者服务、生产者和消费者共同依赖的接口工程&#xff08;打成jar&#xff0c;供生产者和消费者依赖&#xff09;&#xff1b; …

【面试题】前端面试复习6---性能优化

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 性能优化 一、性能指标 要在 Chrome 中查看性能指标&#xff0c;可以按照以下步骤操作&#xff1a; 打开 Chrome 浏览器&#xff0c;并访问你想要测试…