排序思想:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
实现思路:
插入排序一般在数组上实现。采用数组,并使用升序排序举例说明。
1.假设从首元素下标位置开始,我们认为首元素已经有序。
2.取出下一个元素(待插入元素),并记录待插入元素下标位置,认定待插入元素之前的元素已完成排序,然后从后往前进行比较。
3.如果待插入元素小于上一个元素(已排序元素),已排序元素向后移动。
4.重复步骤3,直到找到已排序元素小于或者等于待插入元素的位置。
5.插入已经找到的位置。重复2~5的步骤。
动画演示:
Java代码实现:
public static void insertSort(int[] arr){
for (int i = 0; i < arr.length; i++) {
//记录待插入元素的下标
int index = i;
//待插入元素
int cur = arr[i];
//元素移动
while (index > 0){
//升序排序,后面的元素小于前面的就往前插入
if(cur < arr[index - 1]){
arr[index] = arr[index - 1];
index--;
}else {
break;
}
}
//将待插入元素插入对应位置
arr[index] = cur;
}
}
插入排序算法特性总结:
- 元素集合越接近有序,直接插入排序算法的时间效率越高
- 时间复杂度: O(N^2)
- 空间复杂度: O(1),它是一种稳定的排序算法
- 稳定性:稳定
排序思想:
从下标较小的元素开始,依次比较相邻元素的值,如果发现元素排序顺序不对,进行交换,使元素较大(或者较小)者后移。
实现思路:
1.从下标较小元素开始,比较其相邻元素,顺序不对,进行交换。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数(或者最小的数)。
3.重复以上步骤。
动画演示:
代码实现:
public static void bubbleSort(int[] elem){
//优化冒泡排序,判断在排序时元素是否已经有序,避免不必要的交换
int flag = 0;
for (int i = 0; i < elem.length - 1; i++) {
//elem.length - i 减少比较趟数,在每次交换,放到后面的元素已经排序好了
for (int j = 0; j < elem.length- 1 - i; j++) {
if(elem[j] > elem[j + 1]){
swap(elem,j,j + 1);
flag = 1;
}
}
if(flag == 0){
break;
}
}
}
public static void swap(int[] elem,int start,int end){
int cur = elem[start];
elem[start] = elem[end];
elem[end] = cur;
}
冒泡排序算法特性总结:
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:稳定
排序思想:
希尔排序是记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
实现思路:
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,这里以数组举例。
1.先选定一个小于数组长度N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后将gap减小,例如gap = gap / 2,重复上述操作…
2.当gap = 1时,就相当于整个序列被分到一组,进行一次直接插入排序,此时排序完成。
代码实现:
private static void shellSort(int[] arr) {
int gap = arr.length / 2;
while (gap >= 1){
for (int i = 0; i < arr.length - gap; i++) {
int end = i;
int tmp = arr[end + gap];
while (end >= 0){
if(arr[end] > tmp){
arr[end+gap] = arr[end];
end -= gap;
}else {
break;
}
}
arr[end + gap] = tmp;
}
gap /= 2;
}
}
希尔排序算法特性总结:
时间复杂度:O(N^(1.3~2))
空间复杂度:O(1)
稳定性:不稳定
排序思想:
首先从未排序的序列中选取最大(或者最小)的元素,放到序列的起始位置,然后再从未排序的序列中取出最大(或者最小的元素)放到已排序序列末尾,以此类推,直到所有元素都排序完。
实现思路:
1.假设未排序序列的首元素为最小(或者最大)元素,记录其下标。
2.从最小(或者最大)元素下标下一位置开始和记录的最小(或者最大)元素进行比较,更新最小元素下标,一直比较到未排序序列末尾位置。
3.将我们假设的最小(或者最大)元素和实际找到的最小(或者最大)元素进行交换。
动画演示:
代码实现:
private static void selectSort(int[] arr) {
int minindex = 0;
int index = 0;
for (int i = 0; i < arr.length; i++) {
//假设i下标为最小元素位置
minindex = i;
//从最小元素的下一位置开始寻找
index = minindex + 1;
while (index < arr.length){
if(arr[minindex] > arr[index]){
minindex = index;
}
index++;
}
int tmp = arr[i];
arr[i] = arr[minindex];
arr[minindex] = tmp;
}
}
选择排序算法特性总结:
- 时间复杂度: O(N^2)
- 空间复杂度: O(1)
- 稳定性:不稳定
排序思想:
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。 需要注意的是排升序要建大堆,排降序建小堆。
实现思路:
1.首先建立一个大根堆;
2.把堆首(最大值)和堆尾互换;
3.把堆的尺寸缩小 1,调用建队方法中的向下调整方法,目的是把新的数组顶端数据调整到相应位置;
4.重复步骤 2~3,直到堆的尺寸为 0。
代码实现:
public static void main(String[] args) {
//给定一个数组,将其变成堆的方式存储
int[] heap ={1,2,4,3,5,7,6,9,8,10};
int usedSize = heap.length;
//根据给定的数据建立大根堆
heapCreate(heap,usedSize);
heapSort(heap );
for (int x: heap) {
System.out.print(x + " ");
}
}
//排序
public static void heapSort(int[] heap){
int end = heap.length - 1;
while (end > 0){
swap(0,end,heap);
shiftDown(0,end,heap);
end--;
}
}
//建立大根堆
public static void heapCreate(int[] heap,int usedSize){
//堆排序从最后一个父节点开始调整
for (int parent = (usedSize - 1 - 1) / 2; parent >= 0 ; parent--) {
shiftDown(parent,usedSize,heap);
}
}
//向下调整,结束位置不能超过数组长度
public static void shiftDown(int parent,int len,int[] heap){
// 找到传入父节点孩子节点的下标
int child = parent * 2 + 1;
// 判断孩子节点是否存在
while (child < len){
//判断左右孩子节点的值谁更大
if(child + 1 < len && heap[child] < heap[child + 1]){
child ++;
}
//child下标 一定是左右孩子最大值的下标
if(heap[child] > heap[parent]){
swap(child,parent,heap);
parent = child;
child = 2 * parent + 1;
}else {
break;
}
}
}
public static void swap(int start,int end,int[] heap){
int tmp = heap[start];
heap[start] = heap[end];
heap[end] = tmp;
}
堆排序算法特性总结:
1.时间复杂度: O(N*logN)
2. 空间复杂度: O(1)
3. 稳定性:不稳定
排序思想:
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
挖坑法动图:
排序思路:
1.从数列中挑出一个元素,称为 “基准”(pivot);
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
代码实现:
public static void quickSort(int[] elem,int start,int end){
if(start >= end){
return;
}
//每次寻找基准值
int reference = reference(elem,start,end);
//找左区间的基准值
quickSort(elem,start,reference - 1);
//找右区间的基准值
quickSort(elem,reference+1,end);
}
//挖坑法
public static int reference(int[] elem,int left,int right){
int tmp = elem[left];
while (left < right){
while (left < right && tmp < elem[right]){
right--;
}
elem[left] = elem[right];
while (left < right && tmp > elem[left]){
left ++;
}
elem[right] = elem[left];
}
elem[left] = tmp;
return left;
}
hoare版代码实现:
hoare版和挖坑法思路基本相似,只有微小改动,不再进行过多阐述,自行理解即可。
public static void quickSort(int[] elem,int start,int end){
if(start >= end){
return;
}
//每次寻找基准值
int reference = hoareReference(elem,start,end);
//找左区间的基准值
quickSort(elem,start,reference - 1);
//找右区间的基准值
quickSort(elem,reference+1,end);
}
public static int hoareReference(int[] elem,int left,int right){
int tmp = elem[left];
int i = left;
while (left < right){
while (left < right && tmp <= elem[right]){
right--;
}
while (left < right && tmp >= elem[left]){
left ++;
}
swap(left,right,elem);
}
swap(left,i,elem);
return left;
}
public static void swap(int start,int end,int[] elem){
int tmp = elem[start];
elem[start] = elem[end];
elem[end] = tmp;
}
前后指针法代码实现:
基本思路:
1、选出一个key,并记录其下标位置。
2、起始时,prev指针指向序列开头,cur指针指向prev+1。
3、cur指向元素小于key,prev向后移动一位,交换prev和cur指针指向的内容,然后cur继续向后移动;若cur指向元素大于key,不进行元素交换,cur继续向后移动。直到cur到达end位置,此时将key和++prev指针指向的内容交换即可。
public static void quickSort(int[] elem,int start,int end){
if(start >= end){
return;
}
//每次寻找基准值
int reference = pointerReference(elem,start,end);
//找左区间的基准值
quickSort(elem,start,reference - 1);
//找右区间的基准值
quickSort(elem,reference+1,end);
}
public static int pointerReference(int[] elem,int left,int right){
int prev = left;
int cur = left + 1;
while (cur <= right){
if (elem[cur] < elem[left] && elem[++prev] != elem[cur]){
swap(cur,prev,elem);
}
cur++;
}
swap(prev,left,elem);
return prev;
}
public static void swap(int start,int end,int[] elem){
int tmp = elem[start];
elem[start] = elem[end];
elem[end] = tmp;
}
快速排序优化:
因为每次取的基准值是随机的,我们更希望基准值能将元素均匀分开,使用三数取中法进行优化,并且在数据逐渐趋于有序时,使用插入排序,在这种场景下,插入排序的效率更高。
代码实现:
//挖坑法
public static int reference(int[] elem,int left,int right){
int tmp = elem[left];
while (left < right){
while (left < right && tmp < elem[right]){
right--;
}
elem[left] = elem[right];
while (left < right && tmp > elem[right]){
left ++;
}
elem[right] = elem[left];
}
elem[left] = tmp;
return left;
}
//用三位取中法对快排进行优化
public static void Sort(int[] elem,int start,int end){
int left = start;
int right = end;
if(left >= right){
return;
}
//使用插入排序
//2为随机取的值,具体情况根据使用场景来指定
if(end - start < 2){
insertSort(elem,left,right);
return;
}
//找基准值的下标,使基准值左边的值小于基准值,基准值右边的值大于基准值
//三数取中法找到更适合当基准值的数的下标
int index = midThree(elem,start,end);
//交换更合适当基准值的位置和排序开始的位置
swap(start,index,elem);
int tmp = elem[left];
int benchmark = reference(elem,start,end);
//通过递归再次在基准值左右两边再次找基准
//找左边基准值
quickSort(elem,start,benchmark-1);
//找右边基准值
quickSort(elem,benchmark+1,end);
}
//三数取中法
public static int midThree(int[] array,int left,int right){
int mid = (left+right)/2;
if(array[left] < array[right]){
if(array[mid] < array[left]){
return left;
}else if(array[mid] > array[right]){
return right;
}else {
return mid;
}
}else{
if(array[mid] > array[left]){
return left;
}else if(array[mid] < array[right]){
return right;
}else {
return mid;
}
}
}
//插入排序
public static void insertSort(int[] arr,int left,int right){
for (int i = left + 1; i <= right; i++) {
//记录待插入元素的下标
int index = i;
//待插入元素
int cur = arr[i];
//元素移动
while (index > 0){
//升序排序,后面的元素小于前面的就往前插入
if(cur < arr[index - 1]){
arr[index] = arr[index - 1];
index--;
}else {
break;
}
}
//将待插入元素插入对应位置
arr[index] = cur;
}
}
快速排序算法特性总结:
1.时间复杂度:O(NlogN)
2.空间复杂度:O(NlogN)
稳定性:不稳定
排序思想:
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2路归并。
实现思路:
1.把长度为n的输入序列分成两个长度为n/2的子序列;
2.对这两个子序列分别采用归并排序;
3.将两个排序好的子序列合并成一个最终的排序序列
代码实现:
//递归实现归并排序
public static void mergeSort(int[] array,int left,int right){
if (left >= right){
return;
}
int mid = (left+right)/2;
mergeSort(array,left,mid);
mergeSort(array,mid+1,right);
merge(array,left,right,mid);
}
//对排序好的数据进行合并
public static void merge(int[] array,int start,int end,int mid){
int s1 = start;
int s2 = mid+1;
int[] tmp = new int[end-start+1];
int k = 0;
while (s1 <= mid && s2 <= end ){
if(array[s1] > array[s2]){
tmp[k++] = array[s2++];
}else {
tmp[k++] = array[s1++];
}
}
while (s1 <= mid){
tmp[k++] = array[s1++];
}
while (s2 <= end){
tmp[k++] = array[s2++];
}
for (int i = 0; i < tmp.length; i++) {
array[i+start] = tmp[i];
}
}
归并排序算法特性总结:
1.时间复杂度:O(N*logN)
2.空间复杂度:O(N)
3.稳定性:稳定