1.直接插入排序
1.1 原理
整个区间被分为: 有序区间和无序区间
每次选择无序区间的第一个元素,在有序区间内找个合适的位置插入
1.2 实现
依次查询
public static void insertSort(int[] array){
for (int i = 1; i < array.length; i++) {
// 有序区间: [0, i)
// 无序区间: [i, array.length)
int v = array[i];
int end = i-1;
while (end >= 0 && v < array[end]){
array[end+1] = array[end];
end--;
}
array[end+1] = v;
}
System.out.println(Arrays.toString(array));
}
二分法查询
public static void insertBinSort(int[] array){
for (int i = 1; i < array.length; i++) {
int v = array[i];
int right = i;
int left = 0;
// 需要考虑稳定性
while (left < right){
int mid = (left+right)>>1;
if(v>=array[mid]){
left = mid+1;
}else {
right= mid;
}
}
// 元素搬移
for (int j = i; j > left; j--) {
array[j] = array[j-1];
}
array[left] = v;
}
System.out.println(Arrays.toString(array));
}
1.3 时间复杂度、空间复杂度、稳定性
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定
插入排序,初始数据越接近有序,时间效率越高
2. 希尔排序
2.1 原理
先选定一个整数(N),把待排序文件中所有数据分成组,每隔N隔数是一组,并对每一组内的数据进行排序,然后通过某个式子重新确定N值,在进行分组,再对每组进行排序,重复上述步骤,当N == 1时,所有数据在组内排好序
1.希尔排序是对直接插入排序的优化
2.当gap>1时都只是为了让这一组数据接近有序,整体而言具有优化的效果
2.2 实现
public static void shellSort(int[] array){
int gap = array.length;
while(gap>1){
// 时间复杂度为 O(N^1.25) ~ O(1.6N^1.25)
gap = gap/3+1;
for(int i = gap;i<array.length;i++){
int v = array[i];
int end = i-gap;
while(end >0 && v < array[end]){
array[end+gap] = array[end];
end--;
}
array[end+gap] = v;
}
}
System.out.println(Arrays.toString(array));
}
2.3 时间复杂度、空间复杂度、稳定性
时间复杂度:不确定,gap不同,时间复杂度也不同
空间复杂度:O(1)
不稳定
3. 选择排序
3.1 原理
每一次从无序区间选出最大(最小)的一个元素,存放在无序区间的最后(最前),直到全部待排序的数据元素排完
3.2 实现
一次只找出最大的数
public static void swap(int[] array,int left,int right){
int tmp = array[left];
array[left] = array[right];
array[right] = tmp;
}
public static void selectSort(int[] array){
for (int i = 0; i < array.length-1; i++) {
int maxIndex = 0;
// 找出最大的数
for (int j = 0; j < array.length-i; j++) {
if(array[maxIndex] < array[j]){
maxIndex = j;
}
}
if(maxIndex != array.length-1-i){
swap(array,maxIndex,array.length-1-i);
}
}
System.out.println(Arrays.toString(array));
}
一次找出最大数也找出最小数
public static void selectSortOP(int[] array){
int begin = 0;
int end = array.length-1;
while (begin <= end){
int min = begin;
int max = begin;
int index = begin+1;
while (index <= end){
if(array[index] > max){
max = index;
}
if(array[index] < min){
min = index;
}
index++;
}
// 将最大元素放在最后
if(max != end){
swap(array,max,end);
}
// 如果最小值刚好放在了最后一个位置,就要及时更新min
if(min == end){
min = max;
}
if(min != begin){
swap(array,max,begin);
}
begin++;
end--;
}
System.out.println(Arrays.toString(array));
}
3.3 时间复杂度、空间复杂度、稳定性
时间复杂度:O(N^2)
空间复杂度:O(1)
不稳定
4. 堆排序
4.1 原理
通过堆来选择无序区间的最大的数
排升序建大堆
排降序建小堆
4.2 实现
public static void shiftDown(int[] array,int parents,int size){
int child = parents*2+1;
while (child < size){
// 找左右孩子中 较大的孩子
if(child+1 < size && array[child+1] > array[child]){
child += 1;
}
if(array[child] > array[parents]){
swap(array,child,parents);
parents = child;
child = parents*2+1;
}else {
break;
}
}
}
public static void heapSort(int[] array){
int lastNoLeaf = (array.length-2)>>1;
for (int i = lastNoLeaf; i >= 0; i--) {
shiftDown(array,i,array.length);
}
int end = array.length-1;
while (end >= 0){
swap(array,0,end);
shiftDown(array,0,end);
end--;
}
}
4.3 时间复杂度、空间复杂度、稳定性
时间复杂度:O(NlogN)
空间复杂度:O(1)
不稳定
5. 快速排序
5.1 原理
从区间中取一个数据当作基准值,按照基准值将区间分为两部分
基准值左边都比基准值小,基准值右边都比基准值大
然后再排左半部分,再排右半部分
5.2 实现
// 1.交换法
public static int partition(int[] array,int left,int right){
int key = array[right-1];
int begin = left;
int end = right-1;
while (begin < end){
// 从前往后找比基准值大的元素
while (begin<end && key >= array[begin]){
begin++;
}
// 从后往前找比基准值小的元素
while (begin<end && key <= array[end]){
end--;
}
if(begin < end)
swap(array,begin,end);
}
// 按照基准值划分后,如果基准值刚好在其所在的位置上就不需要交换了
if(begin != right-1)
swap(array,right-1,begin);
return begin;
}
// 2.挖坑法
public static int partition2(int[] array,int left,int right){
int begin = left;
int end = right-1;
int key = array[end];
while (begin < end){
// 从前往后找比基准值大的元素
while (begin < end && array[begin] <= key){
begin++;
}
// 将begin位置的元素填到上一个坑里
if(begin < end)
array[end] = array[begin];
// 从后往前找比基准值小的元素
while (begin < end && array[end] >= key){
end--;
}
// 将end位置的元素填到上一个坑里
if(begin < end)
array[begin] = array[end];
}
// 再把最后一个坑用基准值填上
array[begin] = key;
return begin;
}
// 3. 前后指针法:cur 和 prev 之间隔这的都是比基准值大的元素
public static int partition3(int[] arr,int left,int right){
int cur = left;
int prev = cur-1;
int key = arr[right-1];
while(cur < right){
// 找到比基准值小的位置
//如果prev和cur之间没有元素,那就说明cur前面没有比基准值大的元素
if(arr[cur] < key && ++prev != cur){
swap(arr,cur,prev);
}
cur++;
}
// 如果prev不在基准值的位置上
// 那就说明prev后面有比基准值大的元素
// 所以要将两者交换
if(++prev != right-1){
swap(arr,prev,right-1);
}
return prev;
}
public static void quickSort(int[] array,int left,int right){
if(right - left > 1) {
// 说明区间内至少有两个元素
int part = partition(array, left, right);
// 递归排基准值左侧
quickSort(array, left, part);
// 递归排基准值右侧
quickSort(array, part + 1, right);
}
}
4.3 时间复杂度、空间复杂度、稳定性
时间复杂度:O(n^2) (如果每次基准值取得都是区间的极值)
平均是O(nlogn) (每次都刚好把区间分为两等份)
空间复杂度:O(logn)
稳定性:不稳定
4.4 代码优化
- 取基准值优化
- 1.三数取中法
- 实现:
private static int getMiddleNum(int[] arr,int left,int right){
int mid = left + (right - left) >> 1;
if(arr[left] > arr[right]){
if(arr[right] > arr[mid]){
return right;
}else if(arr[mid] > arr[left]){
return left;
}else
return mid;
}else {
if(arr[right] < arr[mid]){
return right;
}else if(arr[mid] < arr[left]){
return left;
}else
return mid;
}
}
取得中间大的数,这样可以避免取得基准值都是区间的极值这种情况
- 总体优化:当未排序区间小于某个阈值(例如16),使用直接插入进行排序
- 实现:
public static void quickSort(int[] array,int left,int right){
if(right - left < 16) {
insertSort(arr,left,right);
}else {
// 说明区间内至少有16个元素
int part = partition(array, left, right);
// 递归排基准值左侧
quickSort(array, left, part);
// 递归排基准值右侧
quickSort(array, part + 1, right);
}
}
4.5 非递归实现
public static void quickSort(int[] arr,int left,int right){
Stack<Integer> s = new Stack<>();
s.push(right);
s.push(left);
while(!s.empty()){
int start = s.pop();
int end = s.pop();
if(right - left > 1){
int part = partition(arr,start,end);
s.push(end);
s.push(mid+1);
s.push(mid);
s.push(start);
}
}
}
6. 归并排序
6.1 原理
归并排序是采用的先分再归的形式进行排序,首先递归到 区间只有一个数据,然后再进行排序,然后再归到上一层,将两个数在排序,在一直往上归
6.2 实现
// 方法作用是将一个数组分为 左中右 进行排序 [left,mid),[mid,right)
// temp数组中的数字是已经排好序的
public static void mergeData(int[] arr, int left, int mid, int right, int[] temp) {
int begin1 = left;
int begin2 = mid;
int index = left;
// 当两个数组中都有数据时,两组数据进行比较
while (begin1 < mid && begin2 < right) {
if (arr[begin1] <= arr[begin2]) {
temp[index++] = arr[begin1++];
}else {
temp[index++] = arr[begin2++];
}
}
// 当[mid,right) 已经没数据了 temp数组直接将[left,mid)剩余的数据加进去
while (begin1 < mid) {
temp[index++] = arr[begin1++];
}
// 当[left,mid) 已经没数据了 temp数组直接将[mid,right)剩余的数据加进去
while (begin2 < right) {
temp[index++] = arr[begin2++];
}
}
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if (right - left > 1) {
int mid = left + ((right - left) >> 1);
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid, right, temp);
mergeData(arr, left, mid, right, temp);
System.arraycopy(temp, left, arr, left, right - left);
}
}
6.3 时间复杂度、空间复杂度、稳定性
时间复杂度:O(NlogN)
空间复杂度:O(N)
稳定性:稳定
6.4 非递归实现
public static void mergeSortNor(int[] arr) {
int[] temp = new int[arr.length];
int gap = 1;
while (gap < arr.length){
for (int i = 0; i < arr.length; i+= gap*2) {
int left = i;
int mid = left + gap;
int right = mid + gap;
if(mid > arr.length){
mid = arr.length;
}
if(right > arr.length){
right = arr.length;
}
mergeData(arr,left,mid,right,temp);
}
System.arraycopy(temp,0,arr,0,arr.length);
gap <<= 1;
}
}