zoukankan      html  css  js  c++  java
  • 数据结构与算法-排序

    排序是计算机内经常进行的一种操作,其目的是将一组无序的数据元素调整为有序的数据元素

    排序的概念

    排序数学定义:假设含n个数据元素的序列为{ R1, R2, ···, Rn}
    其相应的关键字序列为{ K1, K2, ···, Kn}这些关键字相互之间可以进行比较,即在它们之间存在着这样一个关系 :Kp1 ≤ Kp2 ≤ ··· ≤ Kpn
    按此固有关系将上式记录序列重新排列为{ Rp1, Rp2, ···,Rpn}的操作称作排序

    排序的稳定性

    如果在序列中有两个数据元素r[i]和r[j],它们的关键字k[i] == k [j],且在排序之前,对象
    r[i]排在r[j]前面。如果在排序之后,对象r[i]仍在r[j]前面,则称这个排序方法是稳定的;
    否则称这个排序方法是不稳定的

    多关键字排序

    排序时需要比较的关键字多余一个
    排序结果首先按关键字1进行排序
    当关键字1相同时按关键字2进行排序
    当关键字n-1相同时按关键字n进行排序
    对于多关键字排序,只需要在比较操作时同时考虑多个关键字即可

    排序中的关键操作

    • 比较
      任意两个数据元素通过比较操作确定先后次序
    • 交换
      数据元素之间需要交换才能得到预期结果

    内排序和外排序

    • 内排序
      整个排序过程不需要访问外存便能完成
    • 外排序
      待排序的数据元素数量很大,整个序列的排序过程不可能在内存中完成

    排序的审判

    • 时间性能
      关键性能差异体现在比较和交换的数量
    • 辅助存储空间
      为完成排序操作需要的额外的存储空间
      必要时可以“空间换时间”
    • 算法的实现复杂性
      过于复杂的排序法会影响代码的可读性和可维护性,也可能影响排序的性能

    排序总结

    • 排序是数据元素从无序到有序的过程
    • 排序具有稳定性,是选择排序算法的因素之一
    • 比较和交换是排序的基本操作
    • 多关键字排序与单关键字排序无本质区别
    • 排序的时间性能是区分排序算法好坏的主要因素

    选择排序

    基本思想

    每一趟 (例如第 i 趟,i = 0, 1, …,n-2)在后面 n-i个待排的数据元素中选出关键字
    最小的元素, 作为有序元素序列的第 i 个元素。

    排序过程

    • 首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换
    • 再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换
    • 重复上述操作,共进行n-1趟排序后,排序结束

    示意图

    选择排序
    选择排序

    示例代码

    void SelectionSort(int array[], int len) // O(n*n)
    {
    	int i = 0;
    	int j = 0;
    	int k = -1;
    
    	for (i = 0; i < len; i++)
    	{
    		k = i; //寻找最小元素下标
    		for (j = i + 1; j < len; j++)
    		{
    			if (array[j] < array[k])
    			{
    				k = j;
    			}
    		}
    		if (k != i)
    		{
    			int tmp = array[i];
    			array[i] = array[k];
    			array[k] = tmp;
    		}
    	}
    }
    

    插入排序

    基本思想:

    元素只有一个元素

    排序过程

    整个排序过程为n-1趟插入,即先将序列中第1个记录看成是一个有序子序列,然后从第2个记录开始,逐个进行插入,直至整个序列有序

    示意图

    插入排序
    插入排序.

    示例代码

    void InertionSort(int array[], int len) // O(n*n)
    {
    	int i = 0;
    	int j = 0;
    	int k = -1;
    	int temp = -1;
    
    	for (i = 1; i < len; i++)
    	{
    		k = i; //待插入位置
    		temp = array[k];
    		for (j = i - 1; (j >= 0) && (array[j] > temp); j--)
    		{
    			array[j + 1] = array[j]; //元素后移
    			k = j; //k需要插入的位置
    		}
    		array[k] = temp;//元素插入
    	}
    }
    

    实质

    对线性表执行n-1次插入操作,只是先要找到插入位置

    V[0], V[1], ···, V[i-1]已经排好序。这时已经排好序。这时,用V[i]的关键字与V[i-1], V[i-2], ···的关键字进行比较, 找到插入位置即将V[i]]插入, 原来位置上的对象向后顺移

    插入排序关键点:

    1. 拿出一个元素,留出位置
    2. 符合条件的元素后移

    冒泡排序

    排序过程

    • 将第一个记录的关键字与第二个记录的关键字进行比较,若为逆序r[1].key > r[2].key,则交换;然后比较第二个记录与第三个记录;以此类推,直至n-1个记录和第n个记录比较为止–第一冒泡排序,结果关键字最大的记录被安置在最后一个记录上
    • 对前n-1个记录进行第二趟冒泡排序,结果使关键字次大的记录被安置在第n-1个记录位置
    • 重复上述步骤,直到在一趟排序中没有进行过交换记录的操作为止

    示例代码

    void BubbleSort(int array[], int len) // O(n*n)
    {
    	int i = 0;
    	int j = 0;
    	for (i = 0; i < len; i++)
    	{
    		for (j = len - 1; j > i; j--)
    		{
    			if (array[j] < array[j - 1])
    			{
    				int temp = array[j];
    				array[j] = array[j - 1];
    				array[j - 1] = temp;
    			}
    		}
    	}
    }
    

    排序改进

    在冒泡排序的过程中,可以记录下需要交换的位置,对比交换位置的值,得到一个最值,最后再一次性做交换

    希尔排序

    希尔排序可以看作插入算法的普遍情况

    排序过程

    先取一个正整数d1<n,把所有相隔d1的记录放一组,组内进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止
    O(n-1.3)
    Q(nlogn)
    希尔排序是不稳定的

    示意图

    希尔排序

    示例代码

    void ShellSort(int array[], int len)
    {
    	int i = 0;
    	int j = 0;
    	int k = -1;
    	int temp = -1;
    	int gap = len;
    	do
    	{
    		gap = gap / 3 + 1; //通常取3 O(n 1.3)
    		for (i = gap; i < len; i += gap)
    		{
    			k = i;
    			temp = array[k];
    			for (j = i - gap; (j >= 0) && (array[j] > temp); j -= gap)
    			{
    				array[j + gap] = array[j];
    				k = j;
    			}
    			array[k] = temp;
    		}
    	} while (gap > 1);
    }
    

    快速排序

    思想

    快速排序是对冒泡排序的一种改进。它的基本思想是:
    通过一躺排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一不部分的所有数据都要小,基准数据排在这两个子序列的中间;
    然后再按次方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

    步骤描述

    对r[s···]中记录进行一趟快速排序,附设两个指针i和j,设枢轴记录rp=r[s],x=rp.key
    初始时令i=s,j=t

    • 首先从j所指的位置向前走索第一个关键字小于x的记录,并和rp交换
    • 再从i所指位置起向后搜索,找到第一个关键字大于x的记录,和rp交换
    • 重复上述步骤,直至i==j
    • 再分别对两个子序列进行快速排序,直到每个子序列只含有一个记录为止

    示意图

    快速排序

    示例代码

    //划分过程 第一个元素当枢轴,分成2个有效子序列
    int partition(int array[], int low, int high)
    {
    	int pv = array[low], temp = 0;
    
    	while (low < high)
    	{
    		while ((low < high) && (array[high] >= pv))
    		{
    			high--; //比基准大,本来就在右边,所以high前移动
    		}
    
    		temp = array[low];
    		array[low] = array[high];
    		array[high] = temp;
    
    		while ((low < high) && (array[low] <= pv))
    		{
    			low++;
    		}
    
    		temp = array[low];
    		array[low] = array[high];
    		array[high] = temp;
    	}
    	//返回枢轴的位置。。。重要
    	return low;
    }
    
    //让n个元素 依此减少 减少到1个元素的时候,因为1个元素可以看成一个有序的序列
    void QSort(int array[], int low, int high)
    {
    	if (low < high)
    	{
    		int pivot = partition(array, low, high);
    		//对子序列1排序
    		QSort(array, low, pivot - 1);
    		//对子序列2排序
    		QSort(array, pivot + 1, high);
    	}
    }
    
    void QuickSort(int array[], int len) // O(n*logn)
    {
    	QSort(array, 0, len - 1);
    }
    

    归并排序

    排序思想

    一个元素,可以看做有序的,是稳定的算法

    归并思想

    将两个或两个以上的有序序列合并成一个新的有序序列
    有序序列v[1]···v[m]和v[m+1]···v[n] -> v[1]···v[n]
    只有两路的归并称之为2路归并,还有3路,多路归并
    归并排序

    归并步骤

    检测两个有序序列A和B,C为归并后的新的有序序列
    归并排序

    对一个数组分成两路,mid中间

    1. 设置i,j和p三个指针,其初始值分别指向这三个记录区的起始位置
    2. 合并时依次比较R[i]和R[j]的关键字,取关键字较小的记录复制到R1[p]中
    3. 然后将复制记录的指针i或j加1,以及指向复制位置的指针p加1
    4. 重复这一过程,直至两个输入的子文件中有一个已全部复制完毕,此时将另一个文件中剩余的记录依次复制到R1中即可

    设两个有序的子文件(相当于输入堆)放在同一向量中相邻的位置上:R[low…m],R[m+1…high],先将它们合并到一个局部的暂存向量R1(相当于输出堆)中,待合并完成后将R1复制回R[low…high]中
    二路归并

    示例代码

    void Merge(int src[], int des[], int low, int mid, int high)
    {
    	int i = low;
    	int j = mid + 1;
    	int k = low;
    
    	while ((i <= mid) && (j <= high)) //将小的放到目的地中
    	{
    		if (src[i] < src[j])
    		{
    			des[k++] = src[i++];
    		}
    		else
    		{
    			des[k++] = src[j++];
    		}
    	}
    
    	while (i <= mid)  //若还剩几个尾部元素
    	{
    		des[k++] = src[i++];
    	}
    
    	while (j <= high) //若还剩几个尾部元素
    	{
    		des[k++] = src[j++];
    	}
    }
    
    //每次分为两路 当只剩下一个元素时,就不需要在划分
    void MSort(int src[], int des[], int low, int high, int max)
    {
    	if (low == high) //只有一个元素,不需要归并,结果赋给des[low]
    	{
    		des[low] = src[low];
    	}
    	else //如果多个元素,进行两路划分
    	{
    		int mid = (low + high) / 2;
    		int* space = (int*)malloc(sizeof(int) * max);
    
    		//递归进行两路,两路的划分 
    		//当剩下一个元素的时,递归划分结束,然后开始merge归并操作
    		if (space != NULL)
    		{
    			MSort(src, space, low, mid, max);
    			MSort(src, space, mid + 1, high, max);
    			Merge(space, des, low, mid, high); //调用归并函数进行归并
    		}
    
    		free(space);
    	}
    }
    
    void MergeSort(int array[], int len) // O(n*logn)
    {
    	MSort(array, array, 0, len - 1, len);
    }
    

    排序总结

    排序总结

  • 相关阅读:
    innodb的innodb_buffer_pool_size和MyISAM的key_buffer_size(转自:http://www.java123.net/898181.html)
    mysql max_allowed_packet参数值改大后,莫名被还原
    windows MySQL5.7.9免安装版配置方法
    centos7.2启动级别
    saltstack
    Jmeter -- 参数化(函数助手和CSV数据文件配置)
    Jmeter -- 上下文关联(JSON提取器)
    LBS 基于位置的服务
    uiautomatorviewer报错 Error taking device screenshot: EOF
    python中加入中文注释报错处理
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664702.html
Copyright © 2011-2022 走看看