一、时间复杂度为O(n^2)的排序(冒泡排序、选择排序、插入排序)
1.冒泡排序
快速回忆过程:
1.选择区间[0 ~ n-1],遍历,每次与下一个元素比较,若大于下一个元素,则交换。一轮遍历过后,确保当前子数组的最大元素为最后一个元素。
2.依次选择区间[0 ~ n-2]...[0 ~ 1],执行step1即可。
import java.util.*;
public class BubbleSort {
public int[] bubbleSort(int[] A, int n) {
// 双重循环实现
for(int i = 0; i < n-1; i++){
for(int j = 0; j < n-i-1; j++){
if(A[j] > A[j+1]){
swap(A, j , j+1);
}
}
}
return A;
}
private void swap(int[] array, int index1, int index2){
//交换数组index1和index2位置的元素
int temp = array[index1];
array[index1] = array[index2];
array[index2] = temp;
}
}2.选择排序
快速回忆过程:
1.选择区间[0 ~ n-1],遍历,每次找出该区间的最小元素放在该区间的第一个位置。一轮遍历过后,确保当前子数组的最小元素为第一个元素。
2.依次选择区间[1 ~ n-1]...[n-2 ~ n-1],执行step1即可。
import java.util.*;
public class SelectionSort {
public int[] selectionSort(int[] A, int n) {
// write code here
int currentMin; //当前遍历区间的最小值
int currentMinIndex; //当前遍历区间最小值的索引
for(int i = 0; i < n-1; i++){
//每次遍历区间前,将第一个元素暂视为最小元素
currentMin = A[i];
currentMinIndex = i;
for(int j = i+1; j < n; j++){
if(currentMin > A[j]){
currentMin = A[j];
currentMinIndex = j;
}
}
swap(A, i, currentMinIndex); //区间遍历完成后,交换最小元素和区间的第一个元素
}
return A;
}
private void swap(int[] A, int index1, int index2){
int temp = A[index1];
A[index1] = A[index2];
A[index2] = temp;
}
}3.插入排序
快速回忆过程:
1.选择元素array[1],与array[0]比较,若array[1]更小,则与array[0]交换。
2.一般过程:选择元素array[n],与array[n-1]比较,若array[n]更小,则交换,然后让array[n-1]再与array[n-2]比较,依次类推,直到遍历到array[0]或比较中出现大于关系array[i]>array[i-1]。(也就是说,一次遍历后,子区间一定是递增的)
import java.util.*;
public class InsertionSort {
public int[] insertionSort(int[] A, int n) {
// write code here
int insertIndex; //最终插入的索引
int temp; //暂存当前区间最后一个元素,最终插入到insertIndex的位置。
for(int i = 1; i < n; i++){
//初始化
insertIndex = i;
temp = A[i];
for(int j = i-1; j>=0; j--){
if(temp < A[j]){
A[j+1] = A[j]; //向后移动元素
insertIndex = j; //记录插入位置
}
else{
break;
}
}
A[insertIndex] = temp; //插入
}
return A;
}
}二、时间复杂度为O(n*log n)的排序(归并排序,快速排序,堆排序、希尔排序)
1.归并排序
快速回忆过程:
1.将每个元素变为单独的有序区间,然后依次两两归并成新的有序区间即可。
import java.util.*;
public class MergeSort {
public int[] mergeSort(int[] A, int n) {
// 元素个数小于等于1,返回
if(n <= 1 || A == null)
return A;
int left; //用于记录当前归并集的第一个数组的第一个元素的位置
int mid; //用于记录当前归并集的第二个数组的第一个元素的位置
int right; //用于记录当前归并集的第二个数组的最后一个元素的位置
for(int interval = 1; interval < n; interval = interval*2){ //每轮归并后,归并集大小提升2倍。
//根据当前归并集大小初始化
left = 0;
mid = interval;
right = 2*interval-1;
while(true){
/*这里一共有三种情况:
【1】mid>n-1,说明数组剩余元素数只够一个归并集,那么不用归并,并跳出循环,开始下一轮归并。
【2】mid<=n-1 && right>=n-1,说明数组剩余元素数不能填满第二个归并集,那么进行最后一次归并,并跳出循环,开始下一轮归并。
【3】mid<=n-1 && right<n-1,说明数组剩余元素数完全够2个归并集,那么归并,并进行本轮下一次归并。
*/
if(mid > n-1) break;
if(mid <= n-1 && right >= n-1){
merge(A, left, mid, n-1);
break;
}
else{
merge(A, left, mid, right);
left = left + 2*interval;
mid = mid + 2*interval;
right = right + 2*interval;
}
}
}
return A;
}
private void merge(int[] A, int left, int mid, int right){
//归并函数,相当于已知两个有序数组求它们合并后的有序数组。
int[] temp = new int[right - left + 1];
int i = left;
int j = mid;
int index = 0;
while(i <= mid-1 && j <= right){
if(A[i] <= A[j])
temp[index++] = A[i++];
else
temp[index++] = A[j++];
}
while(i <= mid-1)
temp[index++] = A[i++];
while(j <= right)
temp[index++] = A[j++];
index = left;
for(int element : temp)
A[index++] = element;
}
}2.快速排序
快速回忆过程:
1.随机选择一个元素,进行依次Patition过程,使得它左边的元素小于它,右边的元素大于它。
2.对左右区间递归调用快速排序即可。
Patition过程:
【1】先将划分元素与最后一个元素交换,并定义一个小于等于区间,表示小于等于划分值的区间,初始时长度为0
【2】从左到右遍历所有元素,若大于划分值,则继续遍历,若小于等于划分值,则将小于等于区间长度位置元素与当前位置元素交换,并且小于等于区间向右扩一个位置(例如小于等于区间为空,并且array[5] < 划分值,则array[5]与array[0]交换。小于等于区间变为{array[0]})
【3】遍历完成后,将划分值与小于等于区间的下一个元素交换。
import java.util.*;
public class QuickSort {
public int[] quickSort(int[] A, int n) {
// write code here
if(A == null || n < 2)
return A;
process(A, 0, n-1); //开始排序
return A;
}
private void process(int[] A, int left, int right){
if(left >= right)
return;
int randomIndex = left + (int)(Math.random()*(right - left +1)); //随机划分位置
int mid = patition(A, randomIndex, left, right); //进行一次划分过程,并生成最终划分值的位置
process(A, left, mid-1); //向左递归调用
process(A, mid+1, right); //向右递归调用
}
private int patition(int[] A, int randomIndex, int left, int right){
swap(A, randomIndex, right); //先交换划分值和最后一个元素
int lessEqualSection = left; //指定小于等于区间的下一个位置
for(int i = left; i < right; i++){
if(A[i] <= A[right]){ //元素小于划分值,则小于等于区间下一个位置元素与当前元素交换,小于等于区间增加1
swap(A, lessEqualSection++, i);
}
}
swap(A, lessEqualSection, right); //最终的小于等于区间下一个元素与划分值交换
return lessEqualSection; //返回小于等于区间的下一个元素的位置,即划分值的位置
}
private void swap(int[] A, int index1, int index2){
int temp = A[index1];
A[index1] = A[index2];
A[index2] = temp;
}
}3.堆排序
快速回忆过程:
1.构建大根堆。
2.将大根堆的堆顶元素与最后一个元素交换并将最后一个元素移出堆,作为结果数组的最后一个元素。
3.重新调整堆为大根堆
4.重复step1-3,直到堆中只剩一个元素为止
import java.util.*;
public class HeapSort {
public int[] heapSort(int[] A, int n) {
// write code here
if(A == null)
return A;
for(int i = n/2-1; i >= 0; i--){ //建立大顶堆
adjustHeap(A, i, n);
}
for(int i = n-1; i > 0; i--){ //堆排序过程
swap(A, 0, i); //交换首尾元素
adjustHeap(A, 0, i); //重新调整堆为大顶堆
}
return A;
}
private void adjustHeap(int[] A, int current, int length){
int temp = A[current]; //记录堆顶元素最后赋值,可以减少交换次数
for(int i = 2*current+1; i < length; i = i*2+1){ //从当前节点开始向下调整
if(i+1 < length && A[i+1] > A[i]){ //右孩子大于左孩子,则改为比较右孩子
i++;
}
if(A[i] > temp){ //如果父节点小于子节点,则交换(这里赋值,最后再交换,因此每次用temp比较)
A[current] = A[i];
current = i;
}
else{ //如果父节点大于子节点,则不用调整,退出循环
break;
}
}
A[current] = temp;
}
private void swap(int A[], int index1, int index2){
int temp = A[index1];
A[index1] = A[index2];
A[index2] = temp;
}
}4.希尔排序
快速回忆过程:
希尔排序相当于插入排序的一般化,插入排序的步长为1,而希尔排序的步长逐渐由大到小最后变为1。
希尔排序的时间复杂度是不确定的,当选择合适的步长时,最优可以达到O(n*log n),最差则为O(n^2)。
import java.util.*;
public class ShellSort {
public int[] shellSort(int[] A, int n) {
// write code here
if(A == null || n < 2)
return A;
int temp;
int index;
for(int feet = n/2; feet > 0; feet/=2){ //初始步长为长度的一般,步长不断除以2
for(int i = feet; i < n; i++){ //执行一次插入排序
temp = A[i]; //保存待插入的元素,可以避免交换
index = i; //插入位置
while(index - feet >= 0){
if(temp < A[index - feet]){
A[index] = A[index - feet];
index -= feet;
}
else
break;
}
A[index] = temp;
}
}
return A;
}
}


京公网安备 11010502036488号