目录
c/c++与java的代码实现的整体框架是一样的,区别只在于java的swap函数需要传数组的引用,而c/c++需要传元素的地址
1.选择排序
时间复杂度:最好情况O(n) 最坏情况O(n^2)
空间复杂度:O(1)
对于有n个数的无序数组
- 1.下标[0, n-1]中遍历出最小数的下标minindex和最大数小标maxindex
- 2.下标为minIndex与下标为0的元素交换 maxindex与n-1交换
- 3.下标为[2, n-2]的元素继续上面的操作
在一趟遍历中同时遍历出最小值和最大值,分别放在开头和结尾,效率快一倍
java实现:
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;//数组为空 或者只有一个元素
}
int l = 0;
int r = arr.length - 1;
while (l < r) {
int min = l;
int max = l;
for (int i = l; i <= r; i++) {//区间为[l, r]中寻找 min 和 max
if (arr[max] < arr[i]) {
max = i;
} else if (arr[min] > arr[i]) {
min = i;
}
}
//此时min 和 max 即为 最小 和 最大 元素的下标了 分别和 l 和 r 下标的元素交换
swap(arr, min, l);
if (max == l) {//若max元素下标 == l 上面的swap 会把max元素下标和min交换 就需要if判断一下 把max下标找回
max = min;
}
swap(arr, max, r);
l++;
r--;
}
}
2.冒泡排序
时间复杂度:O(n^2)
空间复杂度:O(1)
左边大就交换,左边小不动,最后大的都在右边
详情还请参考基于冒泡排序的算法研究
3.插入排序
时间复杂度:O(n^2)
空间复杂度:O(1)
- 1.排序下标在[0, 0]内的元素,就一个元素,已经有序
- 2.排序下标在[0, 1]内的元素,大的往后移动,使得[0, 1]内的元素有序
- 3.排序下标在[0, 2]内的元素,使得[0, 2]范围内的元素有序
- 每次都往后移动一个元素,小的就往前移动,使得新的下标范围内的元素有序
- ...
- 4.排序下标[0, n-1]内的元素..
- 5.后面没有元素了,整个数组就排好序了
java实现:
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void insertionSort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i <arr.length ; i++) {//下标范围 [1, sz-1] //从第二项开始
for (int j = i; j >0 ; j--) { // [1, i]
if(arr[j] < arr[j-1]){
swap(arr,j,j-1);
}
}
}
}
4.希尔排序
插入排序的改良版本
时间复杂度:O(N^1.3)
空间复杂度:O(1)
原理:
- 1.选定一个增长量h,按照增长量h作为数据分组的依据,对数组进行分组(每一组两个元素)
- 2.对分好组的每一组数据进行插入排序
- 3.减小增长量,一直减小到1,重复第二步
增长了h确定规则和减小规则:
//h的初始化
int h = 1;
while(h < 数组的长度/2){
h = h*2 + 1;
}
//h的减小规则
h /= 2;
比起传统的插入排序,希尔排序的比较次数和交换次数少了很多
java实现:
public static void shellSort(int[] arr){
int h = 1;
//h的初始化
while( h < arr.length/2){
h = h*2+1;
}
while( h >= 1){
for (int i = h; i <arr.length ; i++) {
for (int j = i; j >=h ; j -= h) {
if(arr[j-h] > arr[j]){
swap(arr,j-h,j);
}
else{
break;
}
}
}
h /= 2;//h的减小
}
5.快速排序
时间复杂度:平均O(n*logn) 最坏O(n^2)
原理:分治+双指针
- 1.确定一个基准数k
- 2.把待排序的数组分为<=k和>=k两边(k具体在哪一边无所谓,且分界点不一定是x)
- 3.分别对两组进行递归处理
其中核心的部分就是第二步:分组
1.设置两个指针i 和 j,分别指向左边界和右边界
2.分别让i 和 j 向中间移动,i指针遇到>=k的停下,j指针遇到<=k的停下
3.交换i和j所指的值
4.直到i和j相遇(或者交错),i左侧就是<=k的数 j右侧就是>=k的数
具体代码实现:
java实现:
void QuickSort(int[] arr, int l, int r){
if(l >= r) return;
int i = l - 1,j = r+1;
int x = arr[l];
while(i < j){
do i++; while(arr[i] < x);
do j--; while(arr[j] > x );
if(i < j){
swap(arr,i,j);
}
}
QuickSort(arr,l,j);
QuickSort(arr,j+1,r);
}
void swap(int[] arr,int i, int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
c/c++实现:
void quick_sort(int q[], int l, int r) {
if (l >= r) return;//l 和 r 不变的
int x = q[l], i = l - 1, j = r + 1;
while (i < j) {
do i++; while (q[i] < x);// 直到 >= x 停止循环
do j--; while (q[j] > x);// 直到 <= x
if (i < j) {
swap(q[i], q[j]);//注意这里是库里的swap函数
}
}
//递归两边 先递归左边 左边排好序 再递归右边
quick_sort(q, l, j),quick_sort(q, j+1, r);
}
边界问题:
当递归区间是[l,j]和[j + 1,r],向下取整基准数x
当递归区间是[l,i - 1]和[i,r],向上取整基准数x
避免:当基准数与递归区间的边界重合,使得下一次递归区间不变,陷入死循环的情况
6.归并排序
时间复杂度:O(n*logn)
原理:分治 + 双指针
- 1.确定分界点:通常取中间点为分界点
- 2.递归排序左右两边
- 3.归并
核心:归并
思路:双指针
c/c++实现:
void merge_sort(int q[], int l, int r)
{
if (l >= r) return;
int mid = l + r >> 1;
//先递归分组
merge_sort(q, l, mid), merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
{
if (q[i] <= q[j]) tmp[k++] = q[i++];//小的放左边
else tmp[k++] = q[j++];
}
// 跳出循环 要判断是否是某一边先走完
while (i <= mid) tmp[k++] = q[i++];
while (j <= r) tmp[k++] = q[j++];
//拷贝到原来的数组
for (i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];
}