常见的排序算法

  本节内容是转载https://blog.csdn.net/yuxin6866/article/details/52771739的,并对部分进行了修改。

public class sort {

	public static void main(String[] args) {
		sort s = new sort();
		int arr[] = { 6,1,2,7,9,3,4,5,10,8 };
		s.bubbleSort(arr);
		s.selectSort(arr);
		s.insertSort(arr);
		s.quickSort(arr, 0, arr.length - 1);

		for (Object o : arr) {
			System.out.print(o + " ");
		}
}

 1.冒泡排序

顾名思义就是整个过程像气泡一样往上升(从小到大),从第一个数开始一次对相邻的两个数进行比较,若前面的数大于后者,则交换位置,一轮比较结束后最大的数位于最后,再进行下一轮比较,......  注意 比较时,不再于最后一个数进行比较。   外层是循环次数,内层是比较次数。

private void bubbleSort(int[] arr) {

		for (int i = 0; i < arr.length - 1; i++) {
			for (int j = 0; j < arr.length - 1 - i; j++) {
				if (arr[j] > arr[j + 1]) {
					int temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}
		}
	}

2.选择排序

以选择小的数为例, 第一轮找出最小的数,和第一个数进行交换;接着从第二个数开始找出次小的数,和第二个数交换,......

注意利用minIndex记录最小数的下标.

private void selectSort(int[] arr) {

		for (int i = 0; i < arr.length - 1; i++) {
			int minIndex = i;
			for (int j = i; j < arr.length - 1; j++) {
				if (arr[j] > arr[j + 1]) {
					minIndex = j + 1;
				}
			}
			int temp = arr[i];
			arr[i] = arr[minIndex];
			arr[minIndex] = temp;
		}
	}

3.插入排序

初始假设第一个数是有序的,第二个数开始和第一个数进行比较,若小于则插入到第一个数的前面(实则是以交换方式);第三数和前两个数进行比较,先和第二个数比,再和第一个数比;......

private void insertSort(int[] arr) {

		for (int i = 1; i < arr.length; i++) {
			int temp = arr[i];
			int j;
			for (j = i; j > 0 && arr[j - 1] > temp; j--) {
				arr[j] = arr[j - 1];
			}
			arr[j] = temp; // arr[j]是比for()中的arr[j]小一个数的,因为j--
		}
	}

4.快速排序

方法其实很简单:分别从初始序列“6  1  2 7  9  3  4  5 10  8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即j=10),指向数字8。

       首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为什么)。哨兵j一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。

 

 

       现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下。

        6  1  2  5  9 3  4  7  10  8

 

        到此,第一次交换结束。接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下。

        6  1  2 5  4  3  9  7 10  8

        第二次交换结束,“探测”继续。哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下。

        3  1 2  5  4  6  9 7  10  8

 

        到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。

private void quickSort(int[] arr, int left, int right) {

		if (left < right) {
			int temp = arr[left];
			int i=left;
			int j=right;
			while (i < j) {
				while (i < j && temp <= arr[j]) {
					j--;
				}
				arr[i] = arr[j];
				while (i < j && temp >= arr[i]) {
					i++;
				}
				arr[j] = arr[i];       //注意这里的arr[i]和上面的arr[i]的i不一样,不是一个值
			}
			arr[i]=temp;
			quickSort(arr,left,j-1);
			quickSort(arr,i+1,right);
		}
	}

5.Shell排序

希尔排序是一种插入排序算法,又称作缩小增量排序。是对直接插入排序算法的改进。其基本思想是: 
先取一个小于n的整数作为第一个增量,把全部数据分成个组。所有距离为的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量重复上述的分组和排序,直至所取的增量,即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法。

public static void shellSort(int[] array) {
    int n = array.length;
    int h;
    for (h = n / 2; h > 0; h /= 2) {
        for (int i = h; i < n; i++) {
            for (int j = i - h; j >= 0; j -= h) {
                if (array[j] > array[j + h]) {
                    int temp = array[j];
                    array[j] = array[j + h];
                    array[j + h] = temp;
                }
            }
        }
    }
}

6. 堆排序

6.1二叉堆的定义

二叉堆是完全二叉树或近似完全二叉树。二叉堆满足两个特性: 

  1)父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。 
  2)每个结点的左子树和右子树都是一个二叉堆。 
当父结点的键值总是大于或等于任何一个子节点的键值时为大根堆。当父结点的键值总是小于或等于任何一个子节点的键值时为小根堆。下面展示一个小根堆: 
这里写图片描述 
由于其它几种堆(二项式堆,斐波纳契堆等)用的较少,一般将二叉堆就简称为堆。

6.2 堆的存储:

一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。 
这里写图片描述

6.3 堆的插入:

每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列,然后将这个新数据插入到这个有序数据中。

6.4 堆排序:

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i],小根堆则相反。 
堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。 
(1)用大根堆排序的基本思想 
  ① 先将初始数组建成一个大根堆,此堆为初始的无序区 
  ② 再将最大的元素(即堆顶)和无序区的最后一个记录交换,由此得到新的无序区和有序区,且满足的值<=的值。 
  ③由于交换后新的根可能违反堆性质,故应将当前无序区调整为堆。然后再次将中最大的元素和该区间的最后一个记录交换,由此得到新的无序区和有序区,且仍满足关系的值<=的值,同样要将调整为堆。 
…… 
直到无序区只有一个元素为止。 
(2)大根堆排序算法的基本操作: 
  ①建堆,建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程,从len/2到0处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(hlen/2) 其中h表示节点的深度,len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。 
  ②调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,因为是沿着深度方向进行调整的。 
  ③堆排序:堆排序是利用上面的两个过程来进行的。首先是根据元素构建堆。然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有节点都取出。

public static void heapSort(int[] array) {
    // 1. 创建最大堆:从最后一个节点的父节点开始
    int lastIndex = array.length - 1;
    int startIndex = (lastIndex - 1) / 2;
    for (int i = startIndex; i >= 0; i--) {
        maxHeap(sort, sort.length, i);
    }
    // 2. 排序:末尾与头交换,逐一找出最大值,最终形成一个递增的有序序列
    for (int i = array.length - 1; i > 0; i--) {
        int temp = array[0];
        array[0] = array[i];
        array[i] = temp;
        maxHeap(array, i, 0);
    }
}

private static void maxHeap(int[] data, int heapSize, int index) {
    // 左子节点
    int leftChild = 2 * index + 1;
    // 右子节点
    int rightChild = 2 * index + 2;
    // 最大元素下标
    int largestIndex = index;
    // 分别比较当前节点和左右子节点,找出最大值
    if (leftChild < heapSize && data[leftChild] > data[largestIndex]) {
        largestIndex = leftChild;
    }
    if (rightChild < heapSize && data[rightChild] > data[largestIndex]) {
        largestIndex = rightChild;
    }
    // 如果最大值是子节点,则进行交换
    if (largestIndex != index) {
        int temp = data[index];
        data[index] = data[largestIndex];
        data[largestIndex] = temp;
        // 交换后,其子节点可能就不是最大堆了,需要对交换的子节点重新调整
        maxHeap(data, heapSize, largestIndex);
    }
}

7.归并排序

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 
归并过程为: 
  比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。 
归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。 
归并操作的工作原理如下: 
  S1: 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列 
  S2: 设定两个指针,最初位置分别为两个已经排序序列的起始位置 
  S3: 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置 
  S4: 重复S3,直到某一指针超出序列尾 
  S5: 将另一序列剩下的所有元素直接复制到合并序列尾

public static void mergeSort(int[] array, int low, int high) {
    int middle = (low + high) / 2;
    if (low < high) {
        mergeSort(array, low, middle);
        mergeSort(array, middle + 1, high);
        merge(array, low, middle, high);
    }
}

public static void merge(int[] array, int low, int middle, int high) {
    int[] temp = new int[high - low + 1];
    int i = low;
    int j = middle + 1;
    int k = 0;
    while (i <= middle && j <= high) {
        if (array[i] < array[j]) {
            temp[k++] = array[i++];
        } else {
            temp[k++] = array[j++];
        }
    }
    while (i <= middle) {
        temp[k++] = array[i++];
    }
    while (j <= high) {
        temp[k++] = array[j++];
    }
    for (int m = 0; m < temp.length; m++) {
        array[m + low] = temp[m];
    }
}

8.基数排序

基数排序的原理如下:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。 
基数排序的方式有以下两种: 
  最高位优先(Most Significant Digit first)法,简称MSD法:先按排序分组,同一组中记录,关键码相等,再对各组按排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码对各子组排序后。再将各组连接起来,便得到一个有序序列。 
  最低位优先(Least Significant Digit first)法,简称LSD法:先从开始排序,再对进行排序,依次重复,直到对排序后便得到一个有序序列。

/**
 * 基数排序(LSD)
 *
 * @param array 待排序数组
 * @param d     表示最大的元素的位数
 */
public static void radixSort(int[] array, int d) {
    int n = 1;
    int times = 1; // 排序次数,由位数最多的元素决定
    int[][] temp = new int[10][array.length]; //数组的第一维表示可能的余数0-9
    int[] order = new int[10]; //数组order用来表示该位是i的元素个数
    while (times <= d) {
        for (int i = 0; i < array.length; i++) {
            int lsd = ((array[i] / n) % 10);
            temp[lsd][order[lsd]] = array[i];
            order[lsd]++;
        }
        int k = 0;
        for (int i = 0; i < 10; i++) {
            if (order[i] != 0) {
                for (int j = 0; j < order[i]; j++) {
                    array[k] = temp[i][j];
                    k++;
                }
                order[i] = 0;
            }
        }
        n *= 10;
        times++;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值