常用排序算法总结 JAVA代码


排序算法:排序大的分类可以分为两种:内排序和外排序。
在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序。下面讲的排序都是属于内排序。 内排序可以分为以下几类:
    1. 插入排序:直接插入排序(稳定)、二分法插入排序(稳定)、希尔排序(不稳定)。
    2. 选择排序:简单选择排序(不稳定)、堆排序(不稳定)。
    3. 交换排序:冒泡排序(稳定)、快速排序(不稳定)。
    4. 归并排序(稳定)
    5. 基数排序(稳定)

插入排序
    • 思想:每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的字序列的合适位置,直到全部插入排序为止。
    • 直接插入排序(从后向前找到合适位置后插入)
      • 直接插入排序是稳定的排序。关于各种算法的稳定性分析可以参考http://www.cnblogs.com/Braveliu/archive/2013/01/15/2861201.html
      • 文件初态不同时,直接插入排序所耗费的时间有很大差异。
      • 若文件初态为正序,则每个待插入的记录只需要比较一次就能够找到合适的位置插入,故算法的时间复杂度为O(n),这是最好的情况。
      • 若初态为反序,则第i个待插入记录需要比较i+1次才能找到合适位置插入,故时间复杂度为O(n^2),这是最坏的情况。
      • 直接插入排序的平均时间复杂度为O(n^2)。
	// 插入排序
	public static void insertionSort(int[] arr) {
		for (int i = 1; i < arr.length; i++) {
			int j = i;
			int temp = arr[i];
			for (; j > 0 && temp < arr[j - 1]; j--) {
				arr[j] = arr[j - 1];
			}
			arr[j] = temp;

		}
	}

  • 二分法插入排序(按二分法找到合适位置插入)

    • 二分插入稳定。二分插入排序的比较次数与待排序记录的初始状态无关,仅依赖于记录的个数
    • 当n较大时,比直接插入排序的最大比较次数少得多。但大于直接插入排序的最小比较次数。
    • 算法的移动次数与直接插入排序算法的相同,最坏的情况为n^2/2,最好的情况为n,平均移动次数为O(n^2)。
  • 希尔排序
    • 基本思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法。

    • 希尔排序不稳定
    • 希尔排序时间性能优于直接插入排序,希尔排序的平均时间复杂度为O(nlogn)
    • 希尔排序就是对直接插入排序的一个优化。比如有这么一种情况:对一个无序数组进行从小到大的排序,但是数组的最后一个位置的数是最小的,我们要把它挪到第一个位置,其他位置的都要往后移动,要是这个数组非常大,那么直接插入排序的开销就非常大。
// 希尔排序
	public static void shellSort(int[] arr) {
		int step = arr.length / 2;
		for (; step > 0; step /= 2) {
			for (int i = step; i < arr.length; i++) {
				int j = i;
				int temp = arr[i];
				for (; j >= step && temp < arr[j - step]; j -= step) {
					arr[j] = arr[j - step];
				}
				arr[j] = temp;
			}
		}
	}
选择排序(简单选择排序、堆排序)
    • 思想:每趟从待排序的 记录序列中选择关键字最小的记录放置到已排序表的最前位置,直到全部排完。
    • 简单选择排序:,
      • 基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。

      • 简单选择排序不稳定
      • 时间复杂度:T(n)=O(n^2)。


	//选择排序
	public static void selectionSort(int[]arr){
		for(int i=0;i<arr.length;i++){
			int min=i;
			for(int j=i+1;j<arr.length;j++){
				if(arr[min]>arr[j]){
					min=j;
				}
			}
			if(i!=min){
				int temp=arr[min];
				arr[min]=arr[i];
				arr[i]=temp;
			}
		}
	}
  • 堆排序
    • 基本思想:堆排序是一种树形选择排序,是对直接选择排序的有效改进。 初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
    • 堆排序不稳定
    • 堆排序优于简单选择排序的原因:
      • 直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。
      • 堆排序可通过树形结构保存部分比较结果,可减少比较次数。
    • 堆排序的最坏时间复杂度为O(nlogn)。堆序的平均性能较接近于最坏性能。由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
//堆排序
	public static void heapSort(int[] arr) {
		
		//第一步建堆
		buildHeap(arr);
		// 要注意每一轮的len是会-1的,heapify要把len传进去,不能用arr.length
		for (int len = arr.length - 1; len >= 0; len--) {
			int temp = arr[0];
			arr[0] = arr[len];
			arr[len] = temp;
			heapify(arr, 0, len);
		}
	}

	public static void buildHeap(int[] arr) {
		int len = arr.length;
		//建堆从最后一个有子节点的节点开始调整,一直到根节点
		for (int i = len / 2 - 1; i >= 0; i--) {
			heapify(arr, i, len);
		}
	}

	public static void heapify(int[] arr, int i, int len) {
		int temp = arr[i];

		// i的左孩子等于2i+1. 左孩子+1是右孩子,选出较大的,再跟arr[i]比较,如果孩子比arr[i]大,就替换掉arr[i]
		for (int child = getLeftChild(i); child < len; i = child, child = getLeftChild(i)) {
			if (child + 1 != len && arr[child + 1] > arr[child]) {
				child++;
			}
			if (arr[child] > temp) {
				arr[i] = arr[child];
			}

			else {

				break;
			}

		}
		arr[i] = temp;

	}

交换排序
    • 冒泡排序
      • 基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
      • 冒泡排序是一种稳定的排序方法。 
        • •若文件初状为正序,则一趟起泡就可完成排序,排序码的比较次数为n-1,且没有记录移动,时间复杂度是O(n)
        • •若文件初态为逆序,则需要n-1趟起泡,每趟进行n-i次排序码的比较,且每次比较都移动三次,比较和移动次数均达到最大值∶O(n^2)
        • •冒泡排序平均时间复杂度为O(n^2)

// 冒泡
	public static void bubbleSort(int[] arr) {
		for (int i = 0; i < arr.length - 1; i++) {
			for (int j = 0; j < arr.length - i - 1; j++) {
				if (arr[j + 1] < arr[j]) {
					int temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}
		}
	}
  • 快速排序
    • 基本思想:(改进冒泡排序;分治法)选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。
      • 快速排序不稳定。
      • 快速排序的时间复杂度为O(nlogn)。
      • 当n较大时使用快排比较好,当序列基本有序时用快排反而不好。

    • // 快排
      	public static void quickSort(int[] arr) {
      		quickSort(arr, 0, arr.length - 1);
      	}
      
      	public static void quickSort(int[] arr, int low, int high) {
      		if (low >= high)
      			return;
      		int pivot = arr[low];
      		int i = low;
      		int j = high;
      		while (i < j) {
      			while (i < j && arr[j] > pivot) {
      				j--;
      			}
      			if (i < j) {
      				arr[i] = arr[j];
      				i++;
      			}
      			while (i < j && arr[i] < pivot) {
      				i++;
      			}
      			if (i < j) {
      				arr[j] = arr[i];
      				j--;
      			}
      		}
      		arr[i] = pivot;
      		quickSort(arr, low, i - 1);
      		quickSort(arr, i + 1, high);
      	}

    • 单链表实现快排:left、mid、right。递归left、right.最后合并起来
      /**
       * Definition for singly-linked list.
       * public class ListNode {
       *     int val;
       *     ListNode next;
       *     ListNode(int x) { val = x; }
       * }
       */
      public class Solution {
          public ListNode sortList(ListNode head) {
              if(head == null || head.next == null) return head;
               
              ListNode left = new ListNode(0), leftHead = left;
              ListNode right = new ListNode(0), rightHead = right;
              ListNode mid = new ListNode(0), midHead = mid;
              int val = head.val;
               
              while(head != null) {
                  if(head.val < val) {
                      left.next = head;
                      left = head;
                  } else if(head.val > val) {
                      right.next = head;
                      right = head;
                  } else {
                      mid.next = head;
                      mid = head;
                  }
                  head = head.next;
              }
               
              left.next = null;
              right.next = null;
              mid.next = null;
              return merge(sortList(leftHead.next), midHead.next, sortList(rightHead.next));
          }
           
          public ListNode merge(ListNode left, ListNode mid, ListNode right) {
              ListNode leftTail = getTail(left);
              ListNode midTail = getTail(mid);
              midTail.next = right;
              if(leftTail != null) {
                  leftTail.next = mid;
                  return left;
              } else {
                  return mid;
              }
          }
           
          public ListNode getTail(ListNode head) {
              if(head == null) return head;
              while(head.next != null) {
                  head = head.next;
              }
              return head;
          }
      }
      

归并排序
    • 基本思想:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

    • 归并排序是稳定的排序方法
    • 归并排序的时间复杂度为O(nlogn)。
    • 速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列。
    • public static void mergeSort(int[] arr) {
      		mergeSort(arr, 0, arr.length - 1);
      	}
      
      	public static void mergeSort(int[] arr, int low, int high) {
      		int mid = (low + high) / 2;
      		if (low < high) {
      			mergeSort(arr, low, mid);
      			mergeSort(arr, mid + 1, high);
      			mergeSortCore(arr, low, mid, high);
      		}
      	}
      
      	public static void mergeSortCore(int[] arr, int low, int mid, int high) {
      		int len = high - low + 1;
      		int[] temp = new int[len];
      		int i = low;
      		int j = mid + 1;
      		int k = 0;
      		while (i <= mid && j <= high) {
      			if (arr[i] < arr[j]) {
      				temp[k++] = arr[i++];
      			} else {
      				temp[k++] = arr[j++];
      			}
      
      		}
      		while (i <= mid) {
      			temp[k++] = arr[i++];
      		}
      		while (j <= high) {
      			temp[k++] = arr[j++];
      		}
      		for (int p = 0; p < temp.length; p++) {
      			arr[low + p] = temp[p];
      		}
      	}
      


基数排序
    • 基本思想:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

    • 基数排序是稳定的排序算法。
    • 基数排序的时间复杂度为O(d(n+r)),d为位数,r为基数
桶排序

关于桶排序先做几点说明:

1)桶排序是稳定的;
2)桶排序是常见排序算法中最快的一种,大多数情况下比快排和归并排序还要快
3)桶排序非常快但是也非常消耗空间,典型的以空间换时间,基本上是最耗内存的一种排序算法。

桶排序中:无序数组有个要求,就是成员隶属于固定(有限的)的区间,如范围为0-9

例如待排数字[6 2 4 1 5 9]

准备10个空桶,最大数个空桶


	public static void bucketSort(int[] arr){
		int [] bucket=new int[10];
		for(int i=0;i<arr.length;i++){
			bucket[arr[i]]=arr[i];
		}
		int cur=0;
		for(int i=0;i<bucket.length;i++){
			if(bucket[i]!=0){
				arr[cur++]=bucket[i];
			}
		}
	}


一、稳定性:
    •  稳定:冒泡排序、插入排序、归并排序和基数排序
    • 不稳定:选择排序、快速排序、希尔排序、堆排序
二、平均时间复杂度
    •   O(n^2):直接插入排序,简单选择排序,冒泡排序。
    •   在数据规模较小时(9W内),直接插入排序,简单选择排序差不多。当数据较大时,冒泡排序算法的时间代价最高。性能为O(n^2)的算法基本上是相邻元素进行比较,基本上都是稳定的。
    •   O(nlogn):快速排序,归并排序,希尔排序,堆排序。
    •   其中,快排是最好的, 其次是归并和希尔,堆排序在数据量很大时效果明显。
三、排序算法的选择
    •   1.数据规模较小
    •     (1)待排序列基本有序的情况下,直接插入排序;
    •     (2)对稳定性不作要求宜用简单选择排序,对稳定性有要求宜用插入或冒泡
    •   2.数据规模不是很大
    •   (1)完全可以用内存空间,序列杂乱无序,对稳定性没有要求,快速排序,此时要付出log(N)的额外空间。
    •   (2)序列本身可能有序,对稳定性有要求,空间允许下,宜用归并排序
    •   3.数据规模很大
    •      (1)对稳定性有要求,则可考虑归并排序。
    •       (2)对稳定性没要求,宜用堆排序
    •   4.序列初始基本有序(正序),宜用直接插入,冒泡


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值