数据结构-排序算法(Java实现)

1. 插入排序

1.1 基本思想

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。

1.2 图解


 

1.3 原理解析

第一趟: 一组数据可以分为有序序列无序序列 , i 表示无序序列的第一个元素,j 表示有序序列的最后一个元素,用 i 下标的元素和 j 下标的元素进行比较,满足条件则交换,j-- 不断往后退,i 逐个比较,比较到 j < 0 停止

  • 具体的交换条件
  1.  先把  i 下标的元素赋值给中间变量(int tmp = arr[i] )
  2.  当  j 下标的元素大于中间变量时,把 j 下标元素赋值到 j+1 的位置,然后中间变量继续依次与 j-- 后所在下标的位置进行比较,直到 j < 0 后停止。
  3.  当 j 下标的元素小于或等于中间变量时,直接把中间变量的元素放到 j+1 的位置即可(也就是原来的位置)

第二趟:i++ 重复上述步骤,直到最后一个元素

1.4 代码实现

    public static void inserSort(int[] array) {
        for (int i = 1; i < array.length; i++) {  //i从1下标开始遍历整个数组
            int tmp = array[i]; //创建一个变量存储数值
            int j = i - 1;  //j的下标位置
            for (; j >= 0; j--) {
                if (array[j] > tmp) { //如果下标j大于变量的值,则需要调整
                    array[j + 1] = array[j];  //放到j+1所在的位置
                } else {
                    //array[j+1] = tmp; //变量放回原位;
                    break; //跳出
                }
            }
            array[j + 1] = tmp; //变量放回原位;
        }
    }

2. 希尔排序(缩小增量排序)

2.1 基本思想

        先选定一个整数,把待排序文件中所有记录分成多个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

2.2 图解

2.3 代码实现

    //希尔排序 不稳定
    public static void shellSort(int[] array) {
        int gap = array.length; //根据元素个数分组数 例如 10
        while (gap > 1) {
            gap /= 2; //5,2,1
            shell(array, gap);
        }
    }

    private static void shell(int[] array, int gap) {
        for (int i = gap; i < array.length; i++) {
            int tmp = array[i];
            int j = i - gap;
            for (; j >= 0; j = j - gap) {  //因为间隔gap个数,所以j要减gap
                if (array[j] > tmp) {  //j下标元素大于tmp变量时,就插入排序交换
                    array[j + gap] = array[j];
                } else {
                    break;
                }
            }
            array[j + gap] = tmp;
        }
    }

 3. 选择排序

3.1 基本思想

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

3.2 图解


 

3.3 代码实现

 //直接选择排序
    //时间复杂度:O(N^2)
    //空间复杂度:O(1)
    //稳定性:不稳定
    public static void selectSort(int[] arrary) {
        for (int i = 0; i < arrary.length; i++) {
            int minIndex = i;  //先把i确定为最小值的下标,后续如果碰到比它小的数值就更新minIndex
            int j = i + 1;  //确定j下标的位置
            for (; j < arrary.length; j++) {
                if (arrary[minIndex] > arrary[j]) {
                    //min一定存储最小值的下标
                    minIndex = j;  //J下标的值更小就更新minIndex的值
                }
            }
            //走到这一步就说明这一趟minIndex存储最小值的下标,然后就可以和i下标的值进行交换
            swap(arrary, i, minIndex);
        }
    }

    private static void swap(int[] array, int i, int j) {
        //交换
        int tmp = array[j];
        array[j] = array[i];
        array[i] = tmp;
    }

双向排序算法(了解)

    public static void selectSort2(int[] arrary) {
        int left = 0;
        int right = arrary.length - 1;
        while (left < right) {
            int minIndex = left;
            int maxIndex = right;
            for (int i = left + 1; i < right; i++) {
                if (arrary[i] < arrary[minIndex]) {
                    minIndex = i;
                }
                if (arrary[i] > arrary[maxIndex]) {
                    maxIndex = i;
                }
            }

            if (maxIndex == left) {
                maxIndex = minIndex;
            }
            swap(arrary, minIndex, left);
            swap(arrary, maxIndex, left);
            left++;
            right--;
        }
    }

    private static void swap(int[] array, int i, int j) {
        //交换
        int tmp = array[j];
        array[j] = array[i];
        array[i] = tmp;
    }

4. 堆排序

4.1 基本思想

堆是一种数据结构,可以把堆看成一棵完全二叉树,这棵完全二叉树满足:任何一个非叶结点的值都不大于(或不小于)其左右孩子结点的值。若父亲大孩子小,则这样的堆叫作大根堆;若父亲小孩子大,则这样的堆叫作小根堆

根据堆的定义知道,代表堆的这棵完全二叉树的根结点的值是最大(或最小)的,因此将一个无序序列调整为一个堆,就可以找出这个序列的最大(或最小)值,然后将找出的这个值交换到序列的最后(或最前),这样,有序序列关键字增加1个,无序序列中关键字减少1个,对新的无序序列重复这样的操作,就实现了排序。这就是堆排序的思想。

堆排序中最关键的操作是将序列调整为堆。整个排序的过程就是通过不断调整,使得不符合堆定义的完全二叉树变为符合堆定义的完全二叉树

4.2 执行过程

建堆是先从自下而上,从右往左

初始堆的每一个结点都要满足堆的定义,也就是父节点的值大于或小于左右孩子结点的值!!!

选出最大值,是将根结点和最后一个结点互换,然后继续构建大顶堆!!!

⭐⭐⭐堆顶和最后一个元素交换,才算一趟,也是该趟的最终序列结果!!!

建堆和排序结果是两个阶段,但同属于一趟中。

4.3 代码实现

    //堆排序
    public void heapSort(int[] array) {
        creatHeap((array));  //建堆
        int end = array.length - 1;  //确定最后一个结点的下标
        //当只剩下一个结点的时候就不用交换
        while (end > 0) {
            //交换
            swap(array, 0, end);
            //向下调整
            siftDown(array, 0, end);
            //调整完一个结点,减1
            end--;
        }

    }

    private void creatHeap(int[] array) {
        for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
            siftDown(array, parent, array.length);
        }
    }
    //向下调整
    private void siftDown(int[] array, int parent, int len) {
        int child = (2 * parent) + 1;  //得到左孩子结点的下标
        while (child < len) {  //小于数组长度说明没调完
            if (child + 1 < len && array[child] < array[child + 1]) {
                //child+1小于usedsize说明没有越界
                //判断左孩子如果比右孩子小的话,child就要指向右孩子,因为下一步要用最大的孩子结点和父亲结点交换
                child = child + 1;
            }
            //程序执行到这一步就说明child一定是左右孩子最大值的下标
            if (array[child] > array[parent]) {  //child和parent找出最大值进行交换
                swap(array, child, parent);
                parent = child;  //继续向下调整
                child = 2 * parent + 1;
            } else {
                break;  //不用交换
            }
        }
    }

 5. 冒泡排序

5.1 基本思想

左边大于右边就交换,一趟排下来最大的放在右边

5.2 图解

5.3 代码实现

    public static void bubbleSort(int[] array) {
        //i代表趟数 10个数据比较的是9趟
        for (int i = 0; i < array.length - 1; i++) {
            boolean flg = false;
            //每一趟从下标0开始比较,每一趟比较都会少i+1次
            for (int j = 0; j < array.length - 1 - i; j++) {
                if (array[j] > array[j + 1]) {
                    swap(array, j, j + 1);
                    flg = true;
                }
            }
            //如果没有交换就证明是有序的
            if (flg == false) {
                break;
            }
        }
    }

6. 快速排序⭐⭐⭐⭐⭐

6.1 基本思想

快速排序时"交换"类的排序,运用了分治的思想,通过一趟排序把将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,直到整个数据变成有序序列。

快速排序算法通过多次的比较和交换来实现排序,排序步骤如下:

  1. 首先从待排序列中选择一个数作为基准值(mid),通过该基准值把数组分成左右两部分。
  2. 大于或等于基准值的数据集中到数组的右边小于基准值的数据集中到数组的左边
  3. 采用分治思想,对左右两个区间数组分别采用上述的方式选择一个数作为基准值,每个区间采用同样的方式把数据分为左右两部分,同样在左边是小于等于该基准值的,右边是大于该基准值的。
  4. 重复上述过程,可以看出是一个递归定义。通过递归将左侧部分排好序后,再递归排右侧部分的顺序,当左、右两个部分都排序好后,整个数组排序都排完了。

6.2 图解

6.3 挖坑法

6.3.1 代码实现

    public static void quickSort(int[] array) {  //为了统一排序算法接口
        quick(array, 0, array.length - 1);
    }

    private static void quick(int[] array, int start, int end) {
        if (start >= end) { //防止end到start后面越界,所以>=
            return;
        }
        //1.三数取中  index是中间大的数字的下标
        int index = middleNum(array, start, end);
        swap(array, index, start);

        int pivot = partition(array, start, end);  //先划分
        
        quick(array, start, pivot - 1); //递归左边

        quick(array, pivot + 1, end);  //递归右边

    }

    private static int middleNum(int[] array, int left, int right) {
        int mid = left + ((right - left) >> 1);

        if (array[left] < array[right]) {
            if (array[mid] < array[left]) {
                return left;    //例:x<3<9
            } else if (array[mid] > array[right]) {
                return right;
            } else {
                return mid;
            }
        } else {  //左边大于右边的
            if (array[mid] < array[left]) {
                return right;    //例:6>4>x
            } else if (array[mid] > array[right]) {
                return left;
            } else {
                return mid;
            }
        }
    }

 6.4 Hoare法

思路:设置两个下标指针 i j ,j向左走,找到比基准值(pivot)小的值先停下来,不交换,i向右走,找到比基准值(pivot)大的值先停下来,然后交换array[i] 和 array[j],当i和j相遇后,交换array[i] 和 array[left]

6.4.1 代码实现

    private static int partitionHoare(int[] array, int left, int right) {
        int tmp = array[left]; // 找一个基准
        int i = left;  //记录left的下标
        while (left < right) {  //直到相遇后退出循环
            while (left < right && array[right] >= tmp) {  //从最右边开始依次找个小于基准的数,
                right--;               //因为会出现右边的数全部大于基准数,right--会出现数组越界的情况,所以再加个left<right条件
            }

            while (left < right && array[left] <= tmp) {  //从最左边开始依次找个大于基准的数
                left++;
            }

            swap(array, left, right);  //经过上述程序步骤得到下标,交换
        }
        swap(array, i, left);  //I下标交换(left和right)下标相遇的位置,只交换值
        return left;  //返回相遇的位置
    }

第二种快速排序,Hoare法,主要是交换的方式不同

    //前后指针法
    private static int partition(int[] array, int left, int right) {
        int tmp = array[left];
        while (left < right) {
            //right往左边走
            while (left < right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];  //把right指向的值给left指向的位置
            //left往右边走
            while (left < right && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left]; //把left指向的值给right的值
        }
        array[left] = tmp; //把基准放回去
        return left;
    }

6.5 前后指针法(了解)

总体思路:prev一直往后走(prev++),当array[prev]小于array[left]的时候,cur++。当array[cur] != array[prev]时。

6.5.1 代码实现

    //前后指针法
    private static int partition(int[] array, int left, int right) {
        int tmp = array[left];
        while (left < right) {
            //right往左边走
            while (left < right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];  //把right指向的值给left指向的位置
            //left往右边走
            while (left < right && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left]; //把left指向的值给right的值
        }
        array[left] = tmp; //把基准放回去
        return left;
    }

7. 归并排序

 7.1 基本思想

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

归并排序算法有两个基本的操作,一个是,也就是把原数组划分成两个子数组的过程。另一个是,它将两个有序数组合并成一个更大的有序数组。

  1. 将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。
  2. 将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。

7.2 图解

假设我们有一个初始数列为{8, 4, 5, 7, 1, 3, 6, 2},整个归并排序的过程如下图所示。

分而治之:

7.3 代码实现

7.3.1 递归版本

  //归并排序

    public static void mergeSort(int[] array) {  //为了接口的统一性
        mergeFunc(array, 0, array.length - 1);
    }

    private static void mergeFunc(int[] array, int left, int right) {
        if (left >= right) {
            return;  //证明递归分解完毕
        }
        int mid = left + ((right - left) >> 1); //确定数组中间的元素  (>>1 是除2)

        //递归左边的子序列(分解)
        mergeFunc(array, left, mid);
        //递归右边的子序列(分解)
        mergeFunc(array, mid + 1, right);

        //左边右边分解完可以开始合并
        merge(array, left, mid, right);
    }

    //合并的方法
    private static void merge(int[] array, int left, int mid, int right) {
        //左子序列从left 开始
        int s1 = left;
        int e1 = mid;

        //右子序列从mid+1开始
        int s2 = mid + 1;
        int e2 = right;

        //创建一个新数组作为复制数组
        int[] tmpArr = new int[right - left + 1];
        int k = 0;

        //每次比较 都是S1和S2进行比较
        //2.如果一个表没有了数据,直接把另一个表剩下的数据全部拷贝进新数组

        //1.保证两个表都有数据
        while (s1 <= e1 && s2 <= e2) {
            if (array[s1] <= array[s2]) {
                //s1小的话就放进新数组
                tmpArr[k++] = array[s1++];  //s1放进数组后,新数组k要++,旧数组s1也要++
            } else {
                //反之s2放进新数组
                tmpArr[k++] = array[s2++];
            }
        }

        //2.看哪个数组还有数据,拷贝回去
        while (s1 <= e1) {  //证明s2已经走完了,直接把剩下的复制到新数组
            tmpArr[k++] = array[s1++];
        }
        while (s2 <= e2) {  //证明s1已经走完了,直接把剩下的复制到新数组
            tmpArr[k++] = array[s2++];
        }

        //把在新数组排好序的拷贝回原数组
        for (int i = 0; i < k; i++) {
            array[i + left] = tmpArr[i];  //拷贝回原数组要+left(下标不一定是从0开始的)
        }
    }

7.3.2 非递归版本

 

    //非递归实现归并排序
    public static void mergeSortNor(int[] array) {
        int gap = 1;
        //最外层控制组数
        while (gap < array.length) {
            //每一层进行排序
            for (int i = 0; i < array.length; i = i + 2 * gap) {
                int left = i;
                int mid = left + gap - 1;
                if(mid >= array.length) {  //预防越界
                    mid = array.length-1;
                }
                int right = mid + gap;
                if(right >= array.length) {  //预防越界
                    right = array.length-1;
                }

                merge(array,left,mid,right);
            }

            gap *= 2;
        }
    }

  • 26
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值