几种常见排序
一、快速插入排序
二、希尔排序
三、选择排序
四、堆排序
五、冒泡排序
六、快速排序
七、归并排序
文章目录
- 几种常见排序
- 前言
- 一、快速插入排序
- 二、希尔排序
- 三、选择排序
- 四、堆排序
- 五、冒泡排序
- 六、快速排序
- 七、归并排序
- 总结
- 排序的稳定性
- 堆排序、快速排序、希尔排序、直接选择排序是不稳定的排序算法,而冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。
前言
将杂乱无章的数据元素,通过一定的方法按关键字顺序排列的过程叫做排序。
排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。分内部排序和外部排序,若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。
一、快速插入排序
(为了方便理解,找了一些动画帮助理解)
插入排序 定义j i;j往前走 找每次比i大的数;然后往后(j+i)放;直到 比i小或者走到0了 停止
i+1进行下一轮
// 时间复杂度O(n^2) 空间复杂度O(1) 如果数组本身就有序 那么选插入排序是最快的 O(n)
//插入排序 定义j i;j往前走 找每次比i大的数;然后往后(j+i)放;直到 比i小或者走到0了 停止
// i+1进行下一轮
// 时间复杂度O(n^2) 空间复杂度O(1) 如果数组本身就有序 那么选插入排序是最快的 O(n)
public static void insertSort(int[] array){
for (int i = 0; i <array.length ; i++) {
int tmp = array[i];
int j = i-1;
for (;j>=0 && tmp < array[j] ;j--){
array[j+1] = array[j];
}
array[j+1] =tmp;
}
}
二、希尔排序
希尔排序 先给数据分组 最好分成素数组例如1、3、5、7;并且最后一定是1组;所以应该先细后粗的分:5、3、1
分完组后 给每一组进行插入排序
//时间复杂度O(n1.3~n1.5) 空间复杂度O(1)
//希尔排序 先给数据分组 最好分成素数组例如1、3、5、7;并且最后一定是1组;所以应该先细后粗的分:5、3、1
//分完组后 给每一组进行插入排序
//时间复杂度O(n^1.3~n^1.5) 空间复杂度O(1)
public void insertSort(int[] array,int gap){
for (int i = gap; i <array.length ; i++) {
int tmp = array[i];
int j = i-gap;
for (;j>=0 && array[j]>tmp;j--){
array[j+gap] = array[j];
}
array[j+gap] = tmp;
}
}
public void shellSort(int[] array){
int[] team = {5,3,1};
for (int i = 0; i < team.length; i++) {
insertSort(array,team[i]);
}
}
三、选择排序
选择排序 定义 i j i为最小的 j往后走 找到比i小的值交换,再往后走
走完一圈找到第一个最小值在0位置,继续i+1找第二个最小值
时间复杂度O(n^2) 空间复杂度O(1)
//选择排序 定义 i j i为最小的 j往后走 找到比i小的值交换,再往后走
// 走完一圈找到第一个最小值在0位置,继续i+1找第二个最小值
//时间复杂度O(n^2) 空间复杂度O(1)
public static void selectSort(int[] array){
for (int i = 0; i < array.length; i++) {
for (int j = i+1; j <array.length ; j++) {
if(array[j] < array[i]){
int tmp = array[j];
array[j] = array[i];
array[i] = tmp;
}
}
}
}
四、堆排序
堆排序 先交换在调整 向上调整 或者 向下调整–确定父亲节点(传入参数)
然后找到孩子节点 比较大小 小的往上放
需要注意的是 调整的时候传入的参数(对应) ,交换的时候每次最后一个换完就要size-1
//时间复杂度O(n^lgn) 空间复杂度O(1)
//堆排序 先交换在调整 向上调整 或者 向下调整--确定父亲节点(传入参数)
// 然后找到孩子节点 比较大小 小的往上放
//需要注意的是 调整的时候传入的参数(对应) ,交换的时候每次最后一个换完就要size-1
//时间复杂度O(n^lgn) 空间复杂度O(1)
public static void swap(int[] array, int top, int len){ //数组,第一个数,最后一个数(每调整一次都会改变 注意传参的时候)
int tmp =array[top];
array[top] = array[len];
array[len] = tmp;
}
public static void shiftDown(int[] array, int parent, int size){
int child = parent*2+1;
while (child < size){
if(child+1 <size && array[child] < array[child+1]){
child++;
}
if(array[child] > array[parent]){
int tmp = array[parent];
array[parent] = array[child];
array[child] = tmp;
parent = child;
child = parent*2+1;
}else {
break;
}
}
}
public static void createHeap(int[] array){
for (int i = (array.length-1-1)/2; i >=0 ; i--) {
shiftDown(array,i,array.length);
}
}
public static void heapSort(int[] array){
//大堆
createHeap(array);
//排序
int end = array.length-1;
while (end > 0){
int tmp = array[0];
array[0] = array[end];
array[end] = tmp;
shiftDown(array,0,end);
end--;
}
}
五、冒泡排序
//冒泡排序 i是排序的总趟数 j是每一趟比较的次数 j没完成一轮就有一个大数到最后成为有序
//时间复杂度O(n^2) 空间复杂度O(1)
public static void bubbleSort(int[] array){
for (int i = 0; i <array.length-1 ; i++) {
boolean flag = false;//优化
for (int j = 0; j < array.length-1-i; j++) {
if(array[j] > array[j+1]){
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
flag = true;
}
}
if(flag==false){
break;
}
}
}
六、快速排序
递归方式实现(挖坑法)
快速排序 从待排序中选出一个基准值(pivot)一般为i下标, 定义两个指针i,j 分别从0 和length-1开始,分别找比基准值小的值,和比基准值大的值
//难点 找到基准值 并且找到每次左右边的基准值,
//快速排序 从待排序中选出一个基准值(pivot)一般为i下标, 定义两个指针i,j 分别从0 和length-1开始,分别找比基准值小的值,和比基准值大的值
//难点 找到基准值 并且找到每次左右边的基准值,
public static void quickSort(int[] array){
quickSortChild(array,0,array.length-1);
}
public static void quickSortChild(int[] array, int low, int high){
if(low >= high) return;
int pivot = partition(array,low,high);
quickSortChild(array,low,pivot-1);
quickSortChild(array,pivot+1,high);
}
public static int partition(int[] array, int left, int right){ //找基准值
int pivot = array[left];
while (left<right){
while (left<right && array[right] >= pivot){
right--;
}
if(array[right] < pivot){ //if-else语句可以直接优化为 array[left] = array[right];
array[left] = array[right]; //此时right 空了
}else {
array[left] = pivot;
break;
}
while (left<right && array[left] <= pivot){
left++;
}
if(array[left] > pivot){ //if-else语句可以直接优化为 array[right] = array[left];
array[right] = array[left];
}else {
array[left] = pivot;
break;
}
}
return left;
}
非递归方式实现
非递归实现 利用栈 还是找基准值 然后分别将 left high放入栈 使得每一个基准值的左边都是小于基准值的 基准值的右边都是大于基准值的
//快速排序 非递归实现 利用栈 还是找基准值 然后分别将 left high放入栈 使得每一个基准值的左边都是小于基准值的 基准值的右边都是大于基准值的
public static void quickSortNor(int[] array){
Stack<Integer> stack = new Stack<>();
stack.push(array.length-1);
stack.push(0);
while (!stack.isEmpty()){
int left = stack.pop();
int right = stack.pop();
if(left >= right){
continue;
}
int pivot = partition(array,left,right);
stack.push(right);
stack.push(pivot+1); //基准值右边右边
stack.push(pivot-1); //基准值左边
stack.push(left);
}
}
七、归并排序
归并排序的非递归实现
外层有一个分组函数来把原数组进行二分,然后调用下方函数,每一次的二分利用临时数组来保存结果,分后的数据进行比较小的放在前面,这样每一组都有序,在进行整理合并,利用合并两个有序数组的思想
public static void merge(int[] array,int low,int mid,int high) {
int[] tmpArr = new int[high-low+1];
int k = 0;// 数组下标
int s1 = low;
int s2 = mid+1;
while (s1 <= mid && s2 <= high) {
if(array[s1] <= array[s2]) {
tmpArr[k++] = array[s1++];
}else {
tmpArr[k++] = array[s2++];
}
}
while (s1 <= mid) { //左边 数据不为空
tmpArr[k++] = array[s1++];
}
while (s2 <= high) { //右边 数据不为空
tmpArr[k++] = array[s2++];
}
//将temp中的元素全部拷贝到原数组中
for (int i = 0; i < tmpArr.length; i++) {
array[i+low] = tmpArr[i]; //放入指定位置 不能从开始放 会覆盖数据导致结果有误
}
}
此处代码在进行分组操作。
public static void mergeSortRec(int[] array,int low,int high) {
if(low >= high) {
return;
}
int mid = (high+low) / 2;
mergeSortRec(array,low,mid);
mergeSortRec(array,mid+1,high);
merge(array,low,mid,high);
}
此处如果对归并排序的递归实现还是不太清楚,可以参考[] 的讲解,由于说明更加清晰明白
总结
排序的稳定性
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。
堆排序、快速排序、希尔排序、直接选择排序是不稳定的排序算法,而冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。