首页 > 其他分享 >数据结构学习记录(四)

数据结构学习记录(四)

时间:2023-09-25 22:34:24浏览次数:36  
标签:排序 记录 int 元素 学习 right 序列 数据结构 left

排序

一、知识要点

1、选择排序

  • 简单选择排序

    • 思想:在未排序的数组中选出一个最大值或最小值与序列首位元素交换,然后在剩下未排序序列再选出最大值或最小值与第二位元素交换,依次类推,直到排序完成

    • typedef int ElementType;
      //太简单了我就不写注释了
      void SSSort(ElementType A[], int N)
      {
      	int i, j, min;
      
      	for(i = 0; i < N - 1; i++)
      	{
      		min = i;
      		for(j = i + 1; j < N; j++)
      		{
      			if(A[min] >A[j])
      				min = j;
      		}
      		int t;
      		t = A[i];
      		A[i] = A[min];
      		A[min] = t;
      	}
      }
      
    • 简单选择排序无论在什么情况下,都需要比较N*(N - 1) / 2次,故其时间复杂度为O(N2)。

  • 堆排序

    • 思想:利用堆输出堆顶元素,或最大值或最小值,其余元素会重新生成堆,继续输出堆顶元素,重复此过程,直到全部元素都已输出。

    • 要得到一个从小到大的序列,想要不另外设置数组来存栈顶元素,可以先把原数组调整为最大堆,然后把堆顶和堆尾元素 i 调换,重新调整最大堆,再把堆顶的i - 1调换,重复此过程,直到i = 0。

    • void HeapSort(ElementType A[], int N)
      {
          //堆排序
          int i;
          //生成最大堆
          for(i = N / 2 - 1; i >= 0; i--)
          {
              PercDown(A, i, N);
          }
          
          //删除堆顶,调整最大堆
          for(i = N - 1; i > 0; i--)
          {
              int t;
              t = A[i];
              A[i] = A[0];
              A[0] = t;
              //调整堆
              PercDown(A, 0, i);	//将堆容量缩小
          }
      }
      
      //将数组以A[p]为根的子堆调整为最大堆
      void PercDown(ElementType A[], int p, int N)
      {
          int Parent, Child;
          ElementType X;
          
          X = A[p];
          for(Parent = p; (Parent*2 + 1) < N; Parent = Child)
          {
              Child = Parent * 2 + 1;
              if(Child != N - 1 && A[Child] < A[Child + 1])
                  Child++;
              if(A[Child] < X)
                  break;	//说明找到了合适位置
              else
                  A[Parent] = A[Child];
          }
          A[Parent] = X;
      }
      

2、插入排序

  • 简单插入排序

    • 思想:将序列分为排好序和未排序两个部分,初始时,已排序序列只有第一个元素,此后将未排序序列的元素逐一插入到已排序序列中,直到未排序序列中元素数为0。

    • #include<stdio.h>
      typedef int ElementType;
      void InsertSort(ElementType A[], int N)
      {
      	int p, i;
      	ElementType Tmp;
      
      	//从未排序序列第一个元素开始,也就是下标为1的元素开始
      	for(p = 1; p < N; p++)
      	{
      		Tmp = A[p];
      		for(i =  p; i > 0 && A[i - 1] > Tmp; i--)	//如果该元素小于左边元素
      			//交换两个元素
      			A[i] = A[i - 1];
      		A[i] = Tmp;	//放进合适位置
      	}
      }
      
    • 该排序有两个嵌套的for循环,所以时间复杂度为O(N2),最快的情况下只执行第一次for循环,时间复杂度为O(N)。

    • 简单选择排序是稳定的排序,因为两个数值相同的元素不会交换位置

  • 希尔排序

    • 思想:将待排序的一组元素按一定间隔分为若干个序列,分别进入插入排序,开始时设置的间隔可以为N / 2,在每轮排序中将间隔逐渐除二,直到间隔为一,最后一步就是简单插入排序。

    • 这过程感觉有点像画画,先排好整体的,再逐步细化……

    • //插入排序的希尔排序
      typedef int ElementType;
      void ShellSort(ElementType arr[], int size)
      {
      	int i;
      	//gap表示希尔增量
      	
      	//希尔增量逐渐缩小
      	for(int gap = size / 2; gap >= 1; gap /= 2)
      	{
      		//从i到N - gap - 1每次进行插入排序
      		for(i = 0; i < size - gap; i++)
      		{
      			//进行插入排序
      			int tmp = arr[i + gap];
      			int j;
      			for(j = i + gap; j >= gap && arr[j - gap] > tmp; j -= gap)
      			{
      				arr[j] = arr[j - gap];
      			}
      			arr[j] = tmp;
      		}
      	}
      }
      
    • 希尔排序的复杂度和简单插入排序一样,也是O(N2),不过其平均复杂度在O(N2)和O(N)之间,一般情况下比简单插入排序效率高。

3、交换排序

  • 冒泡排序

    • 思想:对元素个数为N的待排序列进行排序时,共进行N - 1次循环。在第k次循环中,对从1到N - k个元素从前往后进行比较,每次比较相邻的两个元素,若前一个元素大于(或小于)另一个元素,则两者互换位置,否则保持不变,i指向下一个元素。
    • typedef int ElementType;
      void BubbleSort(ElementType arr[], int N)
      {
      	//冒泡排序,C语言学过,就不写注释了
      	int i, j;
          bool flag;
      	for(i = N - 2; i >= 0; i--)
      	{
              flag = false;
      		for(j = 0; j <= i; j++)
      		{
      			if(arr[j] > arr[j + 1])
      			{
      				ElementType t = arr[j];
      				arr[j] = arr[j + 1];
      				arr[j + 1] = t;
                      flag = true;
      			}
      		}
              if(flag == true)
                  break;
      	}
      
      }
      
    • 时间复杂度为O(N2),在序列已经是排好序的情况下,因为有flag标记,只要进行O(N)次就可以跳出循环。
  • 快速排序

    • 思想:快速排序采用分治法,将问题的规模缩小,然后再分别进行处理。

      • 我们从原序列选择一个主元,将比主元大的元素从右向左放置,比主元小的元素从左向右放置,然后递归快排左右元素。
    • //用快速排序,找一个好的主元是很重要的,可以很好的提高排序效率
      //可以将A[low]、A[high]、A[(low+high) / 2]三者关键字的中值作为主元
      //在序列长度低到某个值时,快速排序的效率很低,可以在低长度序列用简单插排来排序
      //快速排序
      void QuickSort(vector<ElementType> &A, int N)
      {
          //统一接口
          QSort(A, 0, N - 1);
      }
      
      //真快速排序
      //从序列的范围left到right进行快排
      void QSort(vector<ElementType> &A, int left, int right)
      {
          int Cutoff = 30, low, Hight;  //Pivot是主元基准序列号,Cutoff是判断是否切换排序的序列长度
          ElementType Pivot;
          //如果元素充分多,进入快排
          if(Cutoff <= right - left)
          {
              //选择基准
              Pivot = Median(A, left, right);
              low = left;
              Hight = right - 1;  //此时基准在这段序列的Hight处
              //将序列比基准小的放右边,比基准大的放左边
              while(1)
              {
                  while (A[++low] < Pivot);
                  while (A[--Hight] > Pivot);
                  if(low < Hight)   //到这一步说明此时A[low]的值大于主元,A[Hight]的值小于主元,将两元素调换
                      swap(A[low], A[Hight]);
                  else
                      break;//这一步说明该序列元素已比较完毕,此时A[low]的值大于主元,A[Hight]的值小于主元
              }
              //将A[low]和主元调换,使主元回到正确位置
              swap(A[low], A[right - 1]);
              //分别递归解决左边序列和右边序列
              QSort(A, left, low - 1);
              QSort(A, low + 1, right);
          }
          //如果序列长度小于阈值,用插入排序
          else
          {
              InsertSort(A, left, right);
          }
      }
      //选择主元函数
      ElementType Median(vector<ElementType> &A, int left, int right)
      {
          int center = (left + right) / 2;
          //判断三个序列代表的值的大小,给他们安排位置
          if(A[left] > A[center])
              swap(A[left], A[center]);
          if(A[left] > A[right])
              swap(A[left], A[right]);
          if(A[center] > A[right])
              swap(A[center], A[right]);
          //此时A[left] < A[center] < A[right],将A[center]元素与A[right - 1]交换
          swap(A[center], A[right - 1]);
          //只需要考虑A[left + 1]……A[right-2]
          //返回主元
          return A[right - 1];
      }
      //插排
      void InsertSort(vector<ElementType> &A, int left, int right)
      {
      	int i, p;
      	ElementType Tmp;
      
      	//从未排序序列第一个元素开始,也就是下标为1的元素开始
      	for(p = left + 1; p <= right; p++)
      	{
      		Tmp = A[p];
      		for(i = p; i > 0 && A[i - 1] > Tmp; i--)	//如果该元素小于左边元素
      			//交换两个元素
      			A[i] = A[i - 1];
      		A[i] = Tmp;	//放进合适位置
      	}
      }
      
    • 一些复杂的证明显示,快速排序的平均复杂度是O(Nlog2N),但最坏的情况下,它的复杂度就接近冒泡排序了。

4、归并排序

  • 思想:将两个已排序的子序列合并成一个有序序列。归并排序可以用分治法去自上而下的去理解,就是将原序列分成两个等长的子序列,再递归地排序这两个子序列,最后合并成一个完整地有序序列。

  • //归并排序
    // 将原序列和排序序列,以及需要归并的两个子序列的起点位序和右序列的终点传入函数
    void Merge(vector<ElementType>& A, vector<ElementType>& TmpA, int L, int R, int Rend)
    {
    	//归并两个有序序列
    	int Lend, Tmp;
    	//左序列终点的位序
    	Lend = R - 1;
    	//排序序列的起始位置
    	Tmp = L;
    	int left = L;
    	int right = Rend;
    	//比较合并
    	while (L <= Lend && R <= Rend)
    	{
    		if (A[L] <= A[R])
    			TmpA[Tmp++] = A[L++];
    		else
    			TmpA[Tmp++] = A[R++];
    	}
    	//将多余的序列放进排序序列里
    	while (L <= Lend)
    		TmpA[Tmp++] = A[L++];
    	while (R <= Rend)
    		TmpA[Tmp++] = A[R++];
    
    	//最后将TmpA复制回A
    	for (int i = left; i <= right; i++)
    	{
    		A[i] = TmpA[i];
    	}
    }
    //真·归并排序
    //将原序列和排序序列以及他们的左位序和右位序传入函数
    void MSort(vector<ElementType>& A, vector<ElementType> &TmpA, int left, int right)
    {
    	//核心递归排序函数
    	int center;
    
    	if (left < right)
    	{
    		//将序列分为左右两半
    		center = (left + right) / 2;
    		//递归解决左边子序列
    		MSort(A, TmpA, left, center);
    		//递归解决右边子序列
    		MSort(A, TmpA, center + 1, right);
    		//合并两个子序列
    		Merge(A, TmpA, left, center + 1, right);
    	}
    }
    //这是一个用来调整接口的函数
    void MergeSort(vector<ElementType>& A)
    {
    	//创建一个空数组用来存归并后的元素
    	vector<ElementType> TmpA(A.size());
    	//真·归并排序
    	MSort(A, TmpA, 0, A.size() - 1);
    	//清空tmp数组
    	TmpA.clear();
    }
    
  • 相对于快速排序和堆排序,归并排序虽然耗费更多的额外空间,但整体的排序过程是很稳定的。

  • 在实际应用中,开辟大块的额外空间并且将两个数组来回赋值是很耗时的,所以一般不把它用于内部排序,但他是外部排序很有用的工具。

5、基数排序

  • 桶排序

    • 如果已知N个关键字的取值范围是在0~M - 1之间,而M比N小的多,那么可以对关键字的每个可能取值建立M个桶,将每个关键字放入相应的桶中,然后按桶的顺序收集一遍就自然有序了。这就是桶排序。
  • 基数排序

    • 基数排序可看作是桶排序的推广,它所考虑的待排记录包含不止一个关键字。
    • 对于一般有k个关键字的情况,基数排序通常有两种方法:主位优先法和次位优先法
      • 主位优先法:先为最主位关键字建立桶,将每个关键字放入桶中,再按照次位关键字进行排序,依次反复,直到将他们全部顺序收集。
      • 次位优先法:先为最次位关键字建立桶,将每个关键字放入桶中,再按照上位关键字进行排序,依次反复,直到最主位关键字都排好序。
      • 主位优先法基本是分治法的思路,将序列分割成子列后,分别排序再合并结果;而次位优先法是将排序过程分解成了分配和收集这两个相对简单的步骤,不需要分割子列序列,所以一般情况下次位优先法效率更高
  • 单关键字的基数分解

    • 由上面可知基数排序是对有多关键字的对象进行排序,其实可以将单个整型关键字按某种基数分解为多关键字,再进行排序。比如865可以根据基数10将其分解为8、6、5这三个关键字,其中8是最主位关键字,6是次位关键字,5是最次位关键字。

    • //指定基数
      #define Redix 10
      //指定最多关键字
      #define MaxDigit 4
      //根据基数取数字相应的位
      int GetDigit(ElementType X, int D)
      {
      	int d, i;
      	for (i = 1; i <= D; i++)
      	{
      		d = X % Redix;
      		X = X / Redix;
      	}
      	return d;
      }
      //次位优先基数排序
      void LSDRadixSort(vector<ElementType> &A)
      {
      	int Di;
      	//创建十个桶
      	vector<vector<ElementType>> Bucket(Redix);
      	//开始排序
      	for (int D = 1; D <= MaxDigit; D++)	//对数据的每一位循环处理
      	{
      		//分配
      		for (int i = 0; i < A.size(); i++)	//将每个数字按基数分配到各个桶中
      		{
      			Di = GetDigit(A[i], D);	//获得当前位数字
      			//放入桶中
      			Bucket[Di].push_back(A[i]);
      		}
      		//收集
      		int j = A.size() - 1;
      		for (int i = Redix - 1; i >= 0; i--)
      		{
      			//将桶中元素按顺序放入A中
      			while (!Bucket[i].empty())
      			{
      				A[j] = Bucket[i].back();
      				Bucket[i].pop_back();
      				j--;
      			}
      		}
      	}
      }
      

二、感想

排序还算简单,最近还学了C++stl库,书上的一长串代码可可以压缩很多,写得贼舒服

标签:排序,记录,int,元素,学习,right,序列,数据结构,left
From: https://www.cnblogs.com/dragon-dai/p/17729027.html

相关文章

  • 基于Vgg16和Vgg19深度学习网络的步态识别系统matlab仿真
    1.算法运行效果图预览  2.算法运行软件版本MATLAB2022A 3.算法理论概述       步态识别作为生物特征识别领域的一个重要分支,在人体运动分析、身份验证、健康监测等方面具有广泛的应用前景。步态能量图(GaitEnergyImage,简称GEI)是一种有效的步态表示方法,通过......
  • 综合概念映射和网络问题解决方法对学生学习成绩、感知和认知负荷的影响
    (Effectsofanintegratedconceptmappingandweb-basedproblem-solvingapproachonstudents’learningachievements,perceptionsandcognitiveloads) Computers&Education71(2014)77–86一、摘要研究目的:虽然学生可以通过适当的关键词有效地搜索到网络数据,并......
  • express的学习
    在学习了node.js两天之后终于也算是快入门了在node.js环境上陆续学习了fs模块,http快速搭建一个服务,path路径模块,以及npm包管理工具今天主要学习的内容是express框架这个框架的主要作用是简化http的书写只需要constexpress=require("express") constapp=express()即可......
  • Markdowm学习day01
    Markdowm学习标题一级到六级标题用Ctrl1~6字体前加#为一级标题,加两个#为二级标题,以此类推字体Helloworld两边加一个星变斜体/crtl+iHelloworld加两个变粗体/crtl+bHelloworld加三个变斜粗体/crtl+i+bHelloworld加两个波浪号删除/alt+shift+5引用狂神说Java......
  • 日常记录--day9--2023-9月25日--周一
    日程:今天满课,累死了,早上7点起床,吃早饭,去工程实训课,今天上的是机器人实训,造了个小车。下午Java,学了类和对象,晚上7-8点复习了一下,之后进行经典力扣。学了什么:Java让人头疼,来了道力扣题,还要继续加油,继续学习Javaweb。PS:不想学习,想要成为鼠标垫......
  • 编译器优化记录(死代码消除+“激进的”死代码消除)
    编译器优化记录(3)——死代码消除+”激进的“死代码消除0.什么是死代码消除相信大家在写C++的时候,如果你定义了一个变量但是没有对其使用,大部分IDE都会对这个变量进行灰色的染色。又或者说,当你开了一个空的循环,在里面定义并使用了一堆和输出值/返回值没有关系的变量,这个时候IDE......
  • 【学习笔记】(29) 笛卡尔树
    定义与性质笛卡尔树是一种二叉树,每一个结点由一个键值二元组\((k,w)\)构成。要求\(k\)满足二叉搜索树的性质,而\(w\)满足堆的性质。,也就是说,对于一个节点\(i\)的左儿子\(l_i\)和右儿子\(r_i\),一定满足\(l_i<i<r_i\)(下标\(k\)满足二叉搜索树的性质)且\(v_{l_i}\)与......
  • 组合数学学习笔记
    这是一位数学小萌新看oi-wiki的一点点收获。二项式定理二项式定理是组合数学中很基础且很重要的定理,它的式子为:\((a+b)^n=\sum_{i=0}^n\binom{n}{i}a^ib^{n-i}\)可以通过归纳法剖析\((a+b)^n\)的过程证明其正确性。范德蒙德卷积:\(\large\sum_{i=0}^k\binom{n}{i}......
  • 【笔记】机器学习基础 - Ch6.5-6 Kernel Methods
    6.5Sequencekernels考虑拓展\(K:\calX\timesX\to\mathbb{R}\)到\(\calX\)不是向量空间的情况,例如序列、图像等等。现在令\(\calX\)为字符串的集合,对应的核称为序列核sequencekernels;一种序列核的框架,称为rationalkernels,建立在称为加权转换器weightedtransduce......
  • Python学习笔记1
    a="好的,测试字符tester"b=17c=3print(a[1:5])#从第1(包含)个字符取到第5(不包含)个字符print(a[:3])#取到第3个字符(不含3)print(a[-5:-1])#取倒数第5个到倒数第1个print(a[-1:])#取最后一个字符print(len(a))#字符长度#exit()#退出与quit()一样,里面......