排序算法总结与实现

所有算法均使用java语言实现。

算法基本概念

  • 算法稳定性
    如果待排序中有两个元素Ri 与Rj ,其对应的关键字keyi 与keyj ,并且在排序前Ri 在Rj 前面,如果使用某一种排序算法后,Ri 仍然在Rj 前面,则称这个算法是稳定的。否则称这个算法不稳定。算法是否稳定,不是衡量一个算法优劣,主要是针对算法的性质进行描述。
  • 算法时间复杂度
    某一个语句或者块,在算法中被重复执行的次数。算法中所有语句的频度之和记作T(n),时间复杂度主要分析T(n)的数量级,算法中基本的运算的频度与T(n)同数量级,算法的时间复杂度记为:T(n) = O(f(n));
  • 算法空间复杂度
    算法的空间复杂度定义为该算法所消耗的存储空间,它是问题规模n的函数。渐近空间复杂度也常称为空间复杂度。

排序目录

内部排序

排序期间元素全部放在内存中,进行排序

  • 插入排序
    • 直接插入排序
    • 折半插入排序
    • 希尔排序
  • 交换排序
    • 冒泡排序
    • 快速排序
  • 选择排序
    • 简单选择排序
    • 堆排序
  • 归并排序
  • 基数排序

外部排序

在排序期间元素无法全部同时存放在内存中,必须在排序的过程中根据要求不断地在内,外存之间移动的排序。

  • 多路归并排序

排序算法详解

假设待排序的数组是a[0…n-1]

插入排序

直接插入排序

原理:

  1. 初始的时候,a[0]自成一个有序区,无序区为a[1…n-1] 令i=1;
  2. 将a[i]并入当前的有序区a[0…i-1]形成a[0…i]的有序区
  3. i++并且重复第二步 直到i==n-1

插入排序 思想:
首先,它前面的所有元素一定是排好序的,它需要首先找到比它大的数中最小的那一个,然后那个数到当前元素之间的所有元素都后移,然后把当前元素赋值到比它大的数中最小的那个数的位置上。这样就完成了元素插入到合适位置的过程。

算法实现

//方法
	public static void getSort(int [] arr) {
		//首先定义一个指针 ,用来指示需要插入的位置
		int index = 0;
		// 定义一个变量 用来存放 遇到的第一个需要插入的元素
		int temp = 0;
		// 对整个列表进行循环 查找元素( 初始以第一个元素为基准进行排序)
		for(int i=1;i<arr.length;i++) {
			//将前面已经排序好的元素进行遍历(查看一下前面的元素是否已经排序好了,排序好了就直接跳过)
			if(arr[i]>arr[i-1]) continue;
			//查找到第一个乱序的数据 记录该元素所在位置的后一个位置
			index = i-1;
			//从该元素的后一个位置开始,向前回溯,查找需要插入位置的前一个元素。
			while(index>=0 && arr[index]> arr[i]) {
				index -- ;
			}
			//确定插入位置
			index ++;
			//存储需要移动的元素
			temp = arr[i];
			//将元素从插入的位置向后移动(注意 这里的j是大于index的 ,因为我们要将j-1位置的数据向后移动)
			for(int j=i;j>index;j--) {
				arr[j] = arr[j-1];
			}
			//将元素插入
			arr[index] = temp;
		}
	}
  • 算法稳定性分析:
    在排序的过程中,对于前面已经有顺序的序列,某一个元素前面的元素,仍然在该元素前面,所以算法是稳定的。
  • 算法时间复杂度
    算法首先进行了一个for 循环遍历比较查找,然后找到位置后进行了回溯与 向后位置的移动,第一个for 循环嵌套 后面的循环,所以时间复杂度为O(n^2)
  • 空间复杂度
    在排序过程中,只占用了一个额外的存储空间temp 所以空间复杂度为O(1)

折半插入排序

public static void getHalfSele(int arr [] ) {
		//设置一个变量 用来存储需要进行插入的消息
		int temp = 0;
		for(int i=1;i<arr.length;i++) {
			//进行循环便利 找到乱序的第一个位置
			if (arr[i] >arr[i-1]) continue;
			//将查找的值进行存储
			temp = arr[i];
			//进行折半查找
			/**
			 * 初始化位置是从0 到 查找到第一个乱序元素的的 索引-1
			 */
			int start = 0;
			int end =i-1;
			int middle = 0;
			//当开始位置比结束位置小或者相等的时候 退出循环 
			while(start<= end && end>=0) {
				//查找中间元素
				middle = (start+end)/2;
				//如果中间位置的元素 比 temp 大 就说明我们要查找 左边的元素集合
				if (arr[middle]>temp) {
					end = middle-1;
				}else {
					//当比temp小的时候,就查找右边集合的元素 开始位置等于start + 1
					start = middle+1;
				}
			}
			//向后移动 从索引位置的前一个元素 到要插入元素的后一个位置 依次向后移动 
			for(int j =i-1;j>=end+1;j--) {
				arr[j+1]=arr[j];
			}
			//将元素插入
			arr[middle+1] = temp;
		}
	}

希尔排序

直接插入排序算法适用用于基本有序的排序表和数据量不大的排序表,基于这两点,提出了希尔排序,称为缩小增量排序。
基本思想 先将待排序表分割成若干个形如L[i,i+d,i+2d,i+3d,…i+kd]的特殊子表,分别进行插入排序,当整个表中的元素已呈现‘基本有序’的状态时,再对全体记录进行一次直接插入排序。
基本过程: 先获取一个小于n的步长di,把表中全部记录分成di个组,所有距离为di的倍数的记录放在同一个组中,在各组中进行直接插入排序,然后获取第二个步长,d2<d1,重复上述过程,直到所取到的dt = 1即所有记录放在同一个组中,然后在进行直接插入排序,这个时候已经具备很好的局部最优结构,可以获得最优结果。

		public static void shellSort(int [] arr) {
		//获取数组的长度
		int len = arr.length;
		//每次增量都缩小一半,使用位操作符号(这是希尔排序的 d的大小)
		for(int i = len>>1;i>0;i>>=1) {
			// 从初始增量的位置开始计算
			for(int j=i;j<arr.length;j++) {
				//先将该位置的数据放在一个临时变量temp中
				int temp = arr[j];
				//进行组内排序 
				int k = j;
				//当k 大于等于最小步长以及该元素比temp小大的时候,就进行两个数的交换,将比temp大的数字放在后面(插入排序)
				for(;k>=i && arr[k-i]>temp;k-=i) {
					arr[k]= arr[k-i];
				}
				//将小的temp替换为 该组最小索引处 的值 
				arr[k] = temp;
			}
		}
	}
- 分组-> 步长通常为长度的一半
- 组内插入排序
- 回到第一步 (直到间隔为1 停止循环)
  • 时间复杂度 O(N^2)
  • 算法稳定性 在分组的过程中,会出现不稳定的情况
  • 算法使用性:仅适用于顺序存储的线性表

交换排序

冒泡排序

  • 思想:
    冒泡排序的思想是:假设待排序表长为n,从前往后两两比较相邻的元素的值,若为逆序,则交换他们,直到序列比较完为止。
//pao
	public static void PaoSort(int arr[]) {
		//len
		int len = arr.length;
		//对整个数组进行遍历
		for(int i =0;i<arr.length;i++) {
			//比较前面的元素与后面的元素的 值 ,如果值 比后面的大,就与后面的值 交换位置 
			for(int j =arr.length-1;j>i;j--) {
				if(arr[j]<arr[i]) {
					//定义一个临时变量存储后面的值 
					int temp = arr[j];
					arr[j] = arr[i];
					arr[i]=temp;
				}
			}
		}
	}
  • 空间复杂度 因为只使用了一个元素的空间,所以空间复杂度为O(1)
  • 时间复杂度 两层for 循环,所需要的时间复杂度 为 O(N^2)

快速排序

快速排序是冒泡排序的一种改进,其基本思想是基于分治方法的,将待排序的列表根据一个数组中的基准pivote进行划分,一般选择数组的第一个元素作为划分依据,划分为两部分,大于pivote的数组会划分到右边,小于pivote的数组会划分到左边,这样pivote在数组中的位置就是正确的。然后对左边以及右边的部分进行递归调用。

/**
	 * 
	 * @param arr 数组
	 * @param low 0 
	 * @param height arr.length-1
	 */
	public static void QuickSort(int arr[],int low,int height) {
		if(low>height) {
			return ;
		}
		int pivotpos = Partiction(arr,low,height);
		QuickSort(arr,low,pivotpos-1);
		QuickSort(arr,pivotpos+1,height);
		
	}
	//获取每一个部分的第一个元素 作为基准进行 左右划分
	public static int Partiction(int [] arr,int low,int height) {
		int pivot = arr[low];
		while(low<height) {
			while(low<height&& arr[height]>=pivot) --height;
			arr[low]  = arr[height];
			while(low<height&& arr[low]<=pivot) ++ low;
			arr[height] = arr[low];
		}
		arr[low] = pivot;
		return low;
	}
  • 稳定性
    快速排序是一个不稳定的算法,在进行交换的时候他们的相对位置会发生变化。
  • 算法时间复杂度
    时间复杂度平均情况下为O(log2^n)
  • 空间复杂度
    快速排序在排序的过程中,需要用到递归工作栈,其空间复杂度为O(log2^n)
    综合上述情况,快速排序在内部排序算法中是平均性能最优的算法。

选择排序

基本思想: 每一趟(第i趟)在后面n-i+1个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1趟做完,待排序元素只剩下一个就不用选择了。

简单选择排序

简单选择排序的实现使用的是最朴实的选择排序的思想。

//  select
	public static void selectSort(int arr[]) {
		for(int i=0;i<arr.length-1;i++) {
			int temp= arr[i];
			for(int j=i+1;j<arr.length;j++) {
				if(arr[j]<temp) {
					arr[i] = arr[j];
					arr[j] = temp;
					temp=arr[i];
				}
			}
		}
	}
  • 空间复杂度
    仅使用了一个元素空间,因此空间复杂度为O(1)
  • 时间复杂度
    两层for 循环 ,时间复杂度为 n(n-1)/2 ,所以时间复杂度为O(N^2)
  • 稳定性
    在排序的过程中,可能会导致相同的元素交换位置(这儿我认为可以进行控制的),就会导致队列的不稳定。

堆排序

在了解堆排序之前,首先了解一下完全二叉树的概念。

  • 完全二叉树
    一个高度为h,有n个节点的二叉树,当且仅当其每一个节点都与高度为h的满二叉树中编号为1~n 的节点一一对应的时候 ,称为 完全二叉树。简而言之,如果存在度为 1的节点,那么只可能有一个,并且该节点只有左孩子而没有右孩子。
  • 堆的定义
    n个关键字序列L[1…N]称为堆,当且仅当该序列满足:L[i]<=L[2i] && L[I]<=L[2I+1] (小根堆) 或者 L[I]>=L[2i] && L[i]>=L[2i+1] (大根堆)。
    • 构造初始堆
      堆排序的关键就是构造初始堆,对初始序列建堆,就是一个反复筛选的过程。
      • n个节点的完全二叉树,最后一个节点是m=(int)n/2个节点的孩子。对第m个节点为根的子树筛选(对于大根堆,若根结点的关键字小于左右子女中关键字较大者,则交换),使得该子树成为堆。
      • 依次向各结点(m-1 ~1)为根的子树进行筛选,看该节点值是否大于其左右子结点的值,如果不是,就将左右子结点中较大的值与之交换,交换后可能会破坏下一级的堆,然后在采用上述方法构造下一级的堆。
  • 时间复杂度
    建堆的时间复杂度为O(n),调整的时间为O(h),所以在最好、最坏和平均情况下,堆排序的时间复杂度为O(nlog2^n)
  • 空间复杂度
    堆排序在排序的过程中,仅使用了一个辅助空间,因此空间复杂度为O(1)
  • 算法稳定性
    在进行筛选的时候,有可能把后面相同的关键字的元素调整到前面,所以堆排序算法是一种不稳定的算法。

归并排序

归并排序的含义是将两个或者两个以上的有序表组合成一个新的有序表。假定待排序的表含有n个记录,则可以看成是n个有序的子表,每个子表长度为1,然后两两归并,得到n/2(+1)个长度为2 或者1 的有序表;再两两归并~~ 直到合并成为一个长度为n的有序表为止。

  • 空间复杂度
    辅助空间占n个单元,所以归并排序的时间复杂度为O(n)
  • 时间复杂度
    每一趟归并的时间复杂度为O(n),需要进行log2n趟排序,算法的时间复杂度为O(nlog2n)
public class GuiBing {
	public static int [] SortGui(int []a,int low ,int height) {
		int mid = (low+height)/2;
		if(low<height) {
			SortGui(a, low, mid);
			SortGui(a, mid+1, height);
			//左右归并
			merge(a,low,mid,height);
		}
		return a;
	}
	
	private static void merge(int [] a,int low,int mid,int height) {
		int temp [] = new int[height-low+1];
		int i= low;
		int j = mid+1;
		int k = 0;
		// 把较小的数组先移动到新数组中
		while(i<=mid && j<=height){
            if(a[i]<a[j]){
                temp[k++] = a[i++];
            }else{
                temp[k++] = a[j++];
            }
        }
		//把左边剩余的数字移入到新数组中
		while(i<=mid) {
			temp[k++] = a[i++]; 
		}
		//把右边剩余的数字移入到新数组中
		while(j<=height) {
			temp[k++] = a[j++]; 
		}
		//把新数组的数字 覆盖nums 数组
		for(int x = 0;x<temp.length;x++) {
			a[x+low] = temp[x];
		}
	}
	public static void main(String[] args) {
		int [] arr = {1,4,2,5,3,6};
		int [] arr2 = SortGui(arr, 0, arr.length-1);
		for(int i=0;i<arr2.length;i++) {
			System.out.println(arr2[i]);
		}
	}
}

基数排序

基数排序采用多关键字排序的思想,基于关键字各位的大小进行排序。基数排序分为最高位优先和最低位优先排序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值