1.冒泡排序
最好时间复杂度:O(n) ——有序
最坏时间复杂度:O(n^2) ——逆序
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
思想:遍历数组,从数组第一个数开始,依次比较相邻两个数的大小,将大的数交换到后面,经过一轮冒泡后最大的数就在数组的最后,再从头开始,对最大数(已排好序的数)之前的数进行冒泡,重复。
代码:
public static void bubbleSort(long[] arr) {
for(int i = 0; i < arr.length; i++) {
//无序:[0,arr.length - i)
//有序:[arr.length - i,arr.length)
for(int j = 0; j < arr.length - i - 1; j++) {
if(arr[j] > arr[j + 1]) {
long t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
}
}
}
}
优化:
public static void bubbleSort(long[] arr) {
for(int i = 0; i < arr.length; i++) {
//用isSort来记录是否发生了交换
boolean isSort = true;//假设数组有序
//无序:[0,arr.length - i)
//有序:[arr.length - i,arr.length)
for(int j = 0; j < arr.length - i - 1; j++) {
if(arr[j] > arr[j + 1]) {
long t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
isSort = false;//发生了交换
}
}
if(isSort) {
break;
}
}
}
2.插入排序(对区间较小的数排序)
最好时间复杂度:O(n) (数据有序)
最坏时间复杂度:O(n^2) (数据逆序)
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
思想:
分为[有序区间,无序区间],每次从无序区间中的数中选出一个,依次与有序区间的数进行比较,选择合适位置插入
代码:
public static void insertSort(long[] arr) {
//数据一共有arr.length个
for(int i = 0; i < arr.length - 1; i++) {
//分为两个区间
//有序:[0,i]
//无序:[i + 1,arr.length)
//抓出来[i + 1]
long key = arr[i + 1];//无序区间中的数
int j ;
for(j = i; j >= 0; j--) {
if(key < arr[j]) {
arr[j + 1] = arr[j];
} else {
break;
}
}
arr[j + 1] = key;
}
}
3.选择排序
最好时间复杂度:O(n^2)
最坏时间复杂度:O(n^2)
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
思想:遍历数组,每次从无序区间选择最大的一个数与有序区间最前面的数进行交换
代码:
public static void selectSort(long[] arr) {
for(int i = 0; i < arr.length; i++) {
//有序:[arr.length - i,arr.length)
//无序:[0,arr.length - i)
int maxIndex = 0;//最大数的下标
for(int j = 0; j < arr.length - i;j++) {
if(arr[maxIndex] < arr[j]) {
maxIndex = j;
}
}
long t = arr[maxIndex];
arr[maxIndex] = arr[arr.length - i - 1];
arr[arr.length - i - 1] = t;
}
}
4.希尔排序
最好时间复杂度:O(n)
最坏时间复杂度:O(n^2)
平均时间复杂度:O(n^1.3)
空间复杂度:O(1)
稳定性:不稳定
思想:定一个整数gap,把数组分为若干相等的组,再对每个组内的数据排序,再将gap = gap/2,对其进行排序,直到gap = 1
代码:
public static void shallSort(long[] arr) {
int gap = arr.length / 2;
while(true) {
inserSortGap(arr,gap);
if(gap == 1) {
break;
}
gap = gap / 2;
}
}
public static void inserSortGap(long[] arr,int gap) {
for(int i = gap; i < arr.length; i++) {
long key = arr[i];
int j = 0;
for(j = i - gap; j >= 0; j = j - gap) {
if(key < arr[j]) {
arr[j + gap] = arr[j];
} else {
break;
}
}
arr[j + gap] = key;
}
}
5.堆排序
堆排序:基于选择的排序
注意:升序建大堆,降序建小堆
无最好与最坏时间复杂度之分
平均时间复杂度:O(n*log(n))
空间复杂度:O(1)
稳定性:不稳定
代码:
public static void heapSort(long[] arr){
creatHeap(arr,arr.length);//建大堆
//选择过程,arr.length - 1
for(int i = 0; i < arr.length - 1;i++) {
//无序:[0,arr.length - i)
swap(arr,0,arr.length - i- 1) ;
//交换0号下标和无序区间最后一个
//交换后无序:[0,arr.length - i - 1)
//在进行向下调整
adjustDown(arr,arr.length - i - 1,0);
}
}
//建堆(大堆)
public static void creatHeap(long[] arr,int size) {
for(int i = (size - 2)/2; i >= 0; i--) {
adjustDown(arr,size,i);
}
}
//向下调整
public static void adjustDown(long[] arr,int size,int index) {
while(2*index + 1 < size) {
int maxIndex = 2*index + 1;
if(maxIndex + 1 < size && arr[maxIndex + 1] > arr[maxIndex]) {
maxIndex++;
}
if(arr[index] >= arr[maxIndex]) {
break;
}
swap(arr,index,maxIndex);
index = maxIndex;
}
}
//交换
public static void swap(long[] arr,int i,int j) {
long t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
6.快速排序
最好时间复杂度:O(nlog(n))
最坏时间复杂度:O(n^2)
平均时间复杂度:O(nlog(n))
空间复杂度:O(log(n))[最好和平均]~O(n)
稳定性:不稳定
思想:
从待排区间选择一个数(这里选最左边的),将比该数小的放到前面,大的放后面,不断重复,直到小区间有序(size 等于0或1)
快排的优化:
1.挖坑法
2.快排在数量较小的时候不是最快(当区间内的个数低于某个阈值时(16)使用插排)
3.优化选择特殊的数字
随机选
选几个数,选择最中间的值
把相等的值特殊处理
public static void quickSort(long[] arr) {
quickSortInternal(arr,0,arr.length - 1);
}
private static void quickSortInternal(long[] arr,int lowIndex,int highIndex) {
//区间:[lowIndex,highIndex]
int size = highIndex - lowIndex + 1;
if(size <= 1) {
return;
}
//每次选择最左边的数执行partition操作
//keyIndex是经过partition后选出来的数最终所在下标
int keyIndex = partition(arr,lowIndex,highIndex);
//分别对左右区间进行相同处理(递归)
quickSortInternal(arr,lowIndex,highIndex - 1);
quickSortInternal(arr,keyIndex + 1,highIndex);
}
private static int partition(long[] arr,int lowIndex,int highIndex){
//区间:[lowIndex,highIndex]
//选择arr[lowIndex]作为基准值
//需要遍历整个区间和选出来的数进行比较,小于等于的在左边,让大于等于的在右边(无顺序要求)
return partitionHover(arr,lowIndex,highIndex);
//return partition挖坑法(arr,lowIndex,highIndex);//较快
//return partition前后遍历法(arr,lowIndex,highIndex);
}
//法一
private static int partitionHover(long[] arr, int lowIndex, int highIndex) {
int leftIndex = lowIndex;
int rightIndex = highIndex;
long key = arr[lowIndex];
// 选择最左边的数,让右边的先走
// 停止条件:leftIndex == rightIndex
// 循环条件:leftIndex < rightIndex
while (leftIndex < rightIndex) {
while (leftIndex < rightIndex && arr[rightIndex] >= key) {
rightIndex--;
}
//[rightIndex] 遇到了小的
while (leftIndex < rightIndex && arr[leftIndex] <= key) {
leftIndex++;
}
//[leftIndex] 遇到了大的
swap(arr, leftIndex, rightIndex);//交换位置
}
swap(arr, lowIndex, leftIndex);
return leftIndex;
}
//法二 (较重要)
private static int partition挖坑法(long[] arr,int lowIndex,int highIndex) {
int leftIndex = lowIndex;
int rightIndex = highIndex;
long key = arr[lowIndex];
while(leftIndex < rightIndex) {
while(leftIndex < rightIndex && arr[rightIndex] >= key) {
rightIndex--;
}
arr[leftIndex] = arr[rightIndex];
while(leftIndex < rightIndex && arr[leftIndex] <= key) {
leftIndex++;
}
arr[rightIndex] = arr[leftIndex];
}
arr[leftIndex] = key;
return leftIndex;
}
//法三
private static int partition前后遍历法(long[] arr,int lowIndex,int highIndex) {
int separateIndex = lowIndex + 1; //分割
//保证
for(int i = lowIndex + 1;i <= highIndex; i++) {
if(arr[i] < arr[lowIndex]) {//小于时交换位置
swap(arr,i,separateIndex);
separateIndex++;
}
}
swap(arr,lowIndex,separateIndex - 1);
return separateIndex - 1;
}
7.归并排序
最好时间复杂度:O(nlog(n))
最坏时间复杂度:O(nlog(n))
平均时间复杂度:O(n*log(n))
空间复杂度:O(n)
稳定性:稳定
思想:对一个数组平均分成两份,分别对两个区间进行相同排序处理,当区间内数组的个数小于等于1是停止,最后合并有序区间
public static void mergeSort(long[] arr) {
mergeSortInternal(arr, 0, arr.length);
}
// 区间范围左闭右开
// arr[lowIndex, highIndex)
private static void mergeSortInternal(long[] arr, int lowIndex, int highIndex) {
int size = highIndex - lowIndex;//数组个数
if (size <= 1) {
return;
}
int middleIndex = (lowIndex + highIndex) / 2;
// 左区间:[lowIndex, middleIndex)
// 右区间:[middleIndex, highIndex)
mergeSortInternal(arr, lowIndex, middleIndex);//对左边区间进行排序
mergeSortInternal(arr, middleIndex, highIndex);//对右边区间进行排序
// 左右两个区间都有序了
合并两个有序区间(arr, lowIndex, middleIndex, highIndex);//合并有序区间
}
private static void 合并两个有序区间(long[] arr, int lowIndex, int middleIndex, int highIndex) {
int size = highIndex - lowIndex;
long[] extraArray = new long[size];
int leftIndex = lowIndex;
int rightIndex = middleIndex;
int extraIndex = 0;
// 两个队伍都有人
while (leftIndex < middleIndex && rightIndex < highIndex) {
if (arr[leftIndex] <= arr[rightIndex]) {
extraArray[extraIndex] = arr[leftIndex];
extraIndex++;
leftIndex++;
} else {
extraArray[extraIndex] = arr[rightIndex];
extraIndex++;
rightIndex++;
}
}
// 有个队伍没有人
if (leftIndex < middleIndex) {
while (leftIndex < middleIndex) {
extraArray[extraIndex++] = arr[leftIndex++];
}
} else {
while (rightIndex < highIndex) {
extraArray[extraIndex++] = arr[rightIndex++];
}
}
// 最后把数据从新数组统一搬回去
// 新数组 [0, size)
// 搬回去的下标范围 [lowIndex, highIndex)
for (int i = 0; i < size; i++) {
arr[i + lowIndex] = extraArray[i];
}
海量数据的排序问题 ——归并排序(多路归并)
海量数据的特点:内存中存不下,需借助硬盘
步骤:
- 先将数据平均分为n份(每份的大小较小)
- 分别对每份数据进行排序(采用学过的算法)
- 即可得到n个分别有序的数据文件
- 借助内存,进行n个有序数据文件的合并
(将每份文件中最小的数放入内存中【实践中不止一个】,再将最小的数选出来,尾插到最后的有序文件中)