选择排序
基本思想
每一趟从待排序的数据元素中选出最小(或最大)的一个元素,按照顺序放在待排序的数列的最前,直到全部待排序的数据元素排完。
排序过程
例如:
初始:[5 4 6 8 7 1 2 3]
第一趟排序后 1 [4 6 8 7 5 2 3]
第二趟排序后 1 2 [6 8 7 5 4 3]
第三趟排序后 1 2 3 [8 7 5 4 6]
第四趟排序后 1 2 3 4 [7 5 8 6]
第五趟排序后 1 2 3 4 5 [7 8 6]
第六趟排序后 1 2 3 4 5 6 [8 7]
第七趟排序后 1 2 3 4 5 6 7 [8]
最后排序结果 1 2 3 4 5 6 7 8
代码实现:
import java.util.Scanner;
import java.util.Stack;
public class Main {
static void select_Sort(int a[])
{
int n=a.length;
for (int i=0;i<n;i++)
{
int k=i;
for(int j=i+1;j<n;j++)
{
if(a[j]<a[k]) k=j;
}
if(k!=i) {
int temp = a[i];
a[i] = a[k];
a[k] = temp;
}
}
}
public static void main(String[] args)
{
int a[] =new int []{5 ,4 ,6 ,8 ,7, 1, 2 ,3};
select_Sort(a);
for(Object c:a)
System.out.print(c+" ");
}
}
冒泡排序
基本思想
所谓冒泡排序就是依次将两个相邻的数进行比较,大的在前面,小的在后面。
即先比较第一个数和第二个数,大数在前,小数在后,然后比较第 2 个数和第 3 个数,直到比较最后两个数
第一趟排序结束后,最小数的数一定在最后
第二趟排序在第一趟的基础上重复上述操作
由于排序过程中总是大数在前,小数在后,相当于气泡上升,所以叫冒泡排序。
大数在前,小数在后排序后得到的是降序
小数在前,大数在后排序后得到的是升序结果
排序过程(降序)
初始数据:4 5 6 1 2 3
第一趟:
比较前两个数, 4比5小,交换位置 5 4 6 1 2 3
比较第2第3个数, 4比6小,交换位置 5 6 4 1 2 3
比较第3第4个数, 5比1大,位置不变 5 6 4 1 2 3
比较第4第5个数, 1比2小,交换位置 5 6 4 2 1 3
比较最后两个数, 1比3小,交换位置 5 6 4 2 3 1
第一趟结束
第二趟重复第一趟过程得到 6 5 4 3 2 1
排序完毕。
可以发现,第二趟排序结束后,所有数据已经排好序了。实际上,我们在对于一组数据进行冒泡排序时,假如需要排列的数据个数为 n 个,那么 n-1 趟一定能排好序,比如因为第 2 趟都会有前 2 个小的数排序好,n-1 趟前 n-1 小的数已排好序,最后一个数自然也排好序了。
import java.util.Scanner;
import java.util.Stack;
public class Main {
static void BubbleSort(int arr[])
{
int n=arr.length;
for(int i = 0; i < n - 1; i++)
{
for(int j = 0; j < n - i - 1; j++)
{
if(arr[j] > arr[j+1]){
int temp= arr[j+1];
arr[j+1]=arr[j];
arr[j]=temp;
}
}
}
}
public static void main(String[] args)
{
int a[] =new int []{4, 5, 6, 1, 2, 3};
BubbleSort(a);
for(Object c:a)
System.out.print(c+" ");
}
}
插入排序
基本思想
插入排序是一种简单的排序方法,时间复杂度为 O(n*n),适用于数据已经排好序,插入一个新数据的情况。其算法的基本思想是,假设待排序的数据存放在数组 a[1…n] 中,增加一个节点 x 用于保存当前数据,进行比较,a[1]即作为有序区,a[2…n] 作为无序区。
从 i=2 起直至 i=n 为止,通过循环遍历,将 a[i] 放在恰当的位置,使 a[1...i] 数据序列有序
图片描述
x=a[i] 将 x 与前 i-1 个数比较
j=i-1
while(x<a[j]) j-= 1,
将 a 数组的元素从 j 位置开始向后移动:
for k in range(j,i+1,-1):
a[k]=a[k-1]
a[j]=x
生成包含 n 个数据的有序区
例如,我们现在有一个数组 a=[3 2 4 1 6 5 2 7],需要使用插入排序进行排列。
实现过程:
第0步:[3] 2 4 1 6 5 2 7
第1步:[2 3] 4 1 6 5 2 7
第2步:[2 3 4] 1 6 5 2 7
第3步:[1 2 3 4] 6 5 2 7
第4步:[1 2 3 4 6] 5 2 7
第5步:[1 2 3 4 5 6] 2 7
第6步:[1 2 2 3 4 5 6] 7
第7步:[1 2 2 3 4 5 6 7]
代码实现:
import java.util.Scanner;
import java.util.Stack;
public class Main {
static void insert_Sort(int[] a)
{
int len;
len = a.length;
for (int i=0; i<len; i++)
{
int x = a[i];
int j = i - 1;
while( j>=0&&x < a[j] )
{
a[j + 1] = a[j];
j -= 1;
}
a[j + 1] = x;
}
}
public static void main(String[] args)
{
int a[] =new int []{0, 3, 2, 4, 1, 6, 5, 2, 7};
insert_Sort(a);
for(Object c:a)
System.out.print(c+" ");
}
}
快速排序
基本思想
快速排序是一种采用分治法解决问题的一个典型应用,也是冒泡排序的一种改进。它的基本思想是,通过一轮排序将待排记录分割成独立的两部分,其中一部分均比另一部分小,则可分别对这两部分继续进行排序,已达到整个序列有序。排序的时间复杂度为 O(nlogn),相比于简单排序算法,运算效率大大提高。
算法步骤
从序列中取出一个数作为中轴数;
将比这个数大的数放到它的右边,小于或等于他的数放到它的左边;
再对左右区间重复第二步,直到各区间只有一个数。
例如,对以下 10 个数进行快速排序:
6 1 2 7 9 3 4 5 10 8
以第一个数为基准数,在初始状态下,数字 6 在序列的第 1 位,我们的目标是将 6 挪到序列中间的某个位置,假设这个位置是 k 。
现在就需要寻找这个 k ,并且以第 k 位为分界点,左边的数都≤6,右边的数都≥6。那么如何找到这个位置 k 呢?
我们要知道,快速排序其实是冒泡排序的一种改进,冒泡排序每次对相邻的两个数进行比较,这显然是一种比较浪费时间的。
而快速排序是分别从两端开始”探测”的,先从右往左找一个小于 6 的数,再从左往右找一个大于 6 的数,然后交换他们。这里可以用两个变量 i 和 j ,分别指向序列最左边和最右边。
我们为这两个变量起个好听的名字哨兵 i 和哨兵 j。
1、我们首先让哨兵 i 指向序列的最左边,指向数字 6;让哨兵 j 指向序列的最右边,指向数字 8,如下图所示。
图8.3 快速排序初始状态
**2、**首先哨兵 j 开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵 j 先出动,这一点非常重要。
哨兵 j 一步一步地向左挪动,直到找到一个小于 6 的数停下来
然后哨兵 i 再一步一步向右挪动,直到找到一个数大于 6 的数停下来
3、最后哨兵 j 停在了数字 5 面前,哨兵 i 停在了数字 7 面前,如下图所示:
图8.4 快速排序过程
4、 现在交换哨兵 i 和哨兵 j 所指向元素的值,交换之后的序列如下:
在这里插入图片描述
5、 到此,第一次交换结束。接下来开始哨兵 j 继续向左挪动(再友情提醒,每次必须是哨兵 j 先出发)。他发现了 4<6,停下来。哨兵 i 也继续向右挪动的,他发现了 9>6,停下来。此时再次进行交换,交换之后的序列如下
图8.6 快速排序过程
6、第二次交换结束。哨兵 j 继续向左挪动,他发现了 3<6,又停下来。
7、哨兵 i 继续向右移动,此时哨兵 i 和哨兵 j 相遇了,哨兵 i 和哨兵 j 都走到 3 面前。
说明此时“探测”结束。我们将基准数 6 和 3 进行交换。交换之后的序列如下。
图8.7 快速排序一趟结果
到此第一轮“探测”真正结束。
现在基准数 6 已经归位,此时以基准数 6 为分界点,6 左边的数都小于等于 6,6 右边的数都大于等于 6。
现在我们将第一轮“探测”结束后的序列,以 6 为分界点拆分成两个序列,左边的序列是“3 1 2 5 4”,右边的序列是“9 7 10 8”。接下来还需要分别处理这两个序列,因为 6 左边和右边的序列目前都还是混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理 6 左边和右边的序列即可。
实际上快速排序的每一轮处理其实就是将这一轮的基准数归为,直到所有的数都归为为止,排序就结束了。
实现代码:
public class qSort {
public static void qSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>=high){
return;
}
i=low;
j=high;
//temp就是基准位
temp = arr[low];
while (i<j) {
//先看右边,依次往左递减
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左边,依次往右递增
while (temp>=arr[i]&&i<j) {
i++;
}
//如果满足条件则交换
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, low, j-1);
//递归调用右半数组
quickSort(arr, j+1, high);
}
public static void main(String[] args){
int[] arr = new int [1000];
int n;
Scanner in=new Scanner(System.in);
n=in.nextInt();
for(int i=0;i<n;i++)
{
arr[i]=in.nextInt();
}
quickSort(arr, 0, n-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
归并排序
基本思想
归并排序是由递归实现的,主要是分而治之的思想,也就是通过将问题分解成多个容易求解的局部性小问题来解开原本的问题的技巧。
归并排序在合并两个已排序数组时,如果遇到了相同的元素,只要保证前半部分数组优先于后半部分数组, 相同元素的顺序就不会颠倒。所以归并排序属于稳定的排序算法。
每次分别排左半边和右半边,不断递归调用自己,直到只有一个元素递归结束,开始回溯,调用 merge 函数,合并两个有序序列,再合并的时候每次给末尾追上一个最大 int 这样就不怕最后一位的数字不会被排序。
排序过程
代码实现:
public static int[] sort(int[] a,int low,int high){
int mid = (low+high)/2;
if(low<high){
sort(a,low,mid);
sort(a,mid+1,high);
//左右归并
merge(a,low,mid,high);
}
return a;
}
public static void merge(int[] a, int low, int mid, int high) {
int[] temp = new int[high-low+1];
int i= low;
int j = mid+1;
int k=0;
// 把较小的数先移到新数组中
while(i<=mid && j<=high){
if(a[i]<a[j]){
temp[k++] = a[i++];
}else{
temp[k++] = a[j++];
}
}
// 把左边剩余的数移入数组
while(i<=mid){
temp[k++] = a[i++];
}
// 把右边边剩余的数移入数组
while(j<=high){
temp[k++] = a[j++];
}
// 把新数组中的数覆盖nums数组
for(int x=0;x<temp.length;x++){
a[x+low] = temp[x];
}
}
public static void main(String[] args){
int[] arr = new int [1000];
int n;
Scanner in=new Scanner(System.in);
n=in.nextInt();
for(int i=0;i<n;i++)
{
arr[i]=in.nextInt();
}
sort(arr, 0, n-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
希尔排序
基本思想
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法,同时也突破了之前内排序算法复杂度为 O(n2)的限制。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率.
插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
该方法的基本思想是:
先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。
因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
其中增量序列的选择是非常关键的,但通常我们取步长为 n/2(数组长度的一般)然后一直取半直到 1。
public class Shell
{
public static int[] sort(int[] ins,int n){
int gap = n/2;
while(gap > 0){
for(int j = gap; j < n; j++){
int i=j;
while(i >= gap && ins[i-gap] > ins[i]){
int temp = ins[i-gap]+ins[i];
ins[i-gap] = temp-ins[i-gap];
ins[i] = temp-ins[i-gap];
i -= gap;
}
}
gap = gap/2;
}
return ins;
}
public static void main(String[] args)
{
int[] arr = new int [1000];
int n;
Scanner in=new Scanner(System.in);
n=in.nextInt();
for(int i=0;i<n;i++)
{
arr[i]=in.nextInt();
}
int[] ans = sort(arr,n);
for(int a: ans){
System.out.println(a);
}
}
}