排序算法包括:内排序和外排序
内排序包括:插入排序,选择排序,交换排序,归并排序,分配排序
以下排序均指从小到大排序。均通过测试
交换排序
冒泡排序
思想:从左到右扫描,如果左边大于右边则交换,一次排序之后,最大数就排在了最后面,经过n-1次排序就排好了。
时间复杂度:O(n2)
/**
* 交换数组的两个位置值
*
* @param arr
* @param left
* @param right
*/
public static void swap(int arr[], int left, int right) {
// arr[left] = arr[left] + arr[right];
// arr[right] = arr[left] - arr[right];
// arr[left] = arr[left] - arr[right];
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
/**
* 冒泡排序
*
* @param arr
*/
public static void bubbleSort(int[] arr) {
for (int out = arr.length - 1; out > 0; out--) {// 表示已经排好的部分,放在后面
for (int inn = 0; inn < out; inn++) {// 从头开始排未排好的部分,
if (arr[inn] > arr[inn + 1]) {
swap(arr, inn, inn + 1);
}
}
}
}
快速排序
快速排序属于交换排序,但是采用了分治思想,时间复杂度优化不少为O(nlogn)
第一步:主要是划分算法partition(),取一个哨兵位,遍历数组找出比哨兵小的将其放在左半部分,最后将哨兵归位到中间位置,左半部分是小于哨兵的右半部分必然是大于哨兵。
第二步:递归执行划分数组的左半部分和右半部分。
java实现:
/**
* 快速排序的划分算法
*
* @param arr
* @param start
* @param end
* @return
*/
public static int partition(int[] arr, int start, int end) {
int pivot = arr[end];// 取最后一个元素为哨兵
// 比哨兵大的第一个元素的位置,也是小于哨兵的最后一个元素
int smallIndex = start;
// 从头遍历start--end的元素,找到比pivot小的元素和samllindex交换
for (int travel = start; travel < end; travel++) {
if (arr[travel] < pivot ) {//有时候会自己和自己交换
System.out.println("交换" + smallIndex + "和" + travel);
if(smallIndex!=travel){
swap(arr, smallIndex, travel);
}
System.out.println("交换" + smallIndex + "和" + travel+"后");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
smallIndex++;
}
}
swap(arr, smallIndex, end);
return smallIndex;
}
/**
* 递归实现快速排序
*
* @param arr
* @param start
* @param end
*/
public static void quickSort(int[] arr, int start, int end) {
if (start > end) {
return; // 记得返回
}
int partition = partition(arr, start, end);
quickSort(arr, start, partition - 1);// 不加减会stack over flow
quickSort(arr, partition + 1, end);
}
选择排序
直接选择排序
选择排序优化了冒泡排序,减少了交换次数到O(n),但是比较次数依然是O(n2)。
思想:左边是已排序区,右边为未排序区,每次从右边选取出最小的数,放到已排序的后面。
实现:外层循环0-n-1表示已排序最后的位置,内层从已排序最后位置开始。
时间复杂度:O(n2)
java实现:
/**
* 选择排序
*
* @param arr
*/
public static void selectSort(int[] arr) {
int minusInex = 0;// 保存最小值的位置
for (int out = 0; out < arr.length - 1; out++) {
//arr[minusInex] = arr[out];
minusInex = out;
int inner;
for (inner = out + 1; inner < arr.length; inner++) {
if (arr[inner] < arr[minusInex]) {
minusInex = inner;// 注意保存最小值位置
}
}
swap(arr, out, minusInex);
}
}
堆排序
首先说明一下什么是堆,堆是一种完全二叉树构成的逻辑结构,存储结构是数组.所以数组存储的完全二叉树满足:
根节点下标为 i ,左子节点下标为 2i+1,右子节点下标为 2i+2.
反之,知道左或右子节点下标 j ,根节点为 i= (j-1)/2 ,或i=(j-2)/2.
根节点小于左右子节点为小根堆,根节点大于左右子节点为大根堆.
什么是堆排序
从堆中每次取出顶部元素,之后再对其余元素进行堆调整,顶部元素又是最小元素,这样依次取出最小元素的结果就是进行了排序.
堆排序主要的考虑操作是,创建堆和取出堆顶元素之后调整堆
首先,调整堆
堆排序时取出顶元素的操作实际上是,将堆最后一个元素与堆顶元素进行交换的,这样最后一个元素行成有序区,接着调整堆,之后再将倒数第二个元素与顶元素交换行成两个元素的有序区.
调整堆从堆顶开始,自顶向下进行比较,头结点向下调整,将较大的子节点向上调整到顶元素的位置,头结点调整为对应位置的子元素,接着从子节点的位置继续调整.
创建堆,是从最后一个非叶子结点开始进行堆调整,直到堆顶
最后一个非终端元素的下标是[n/2]向下取整,所以筛选只需要从第[n/2]向下取整个元素开始,从后往前进行调整。
比如,给定一个数组,首先根据该数组元素构造一个完全二叉树。
然后从最后一个非叶子结点开始,每次都是从父结点、左孩子、右孩子中进行比较交换,交换可能会引起孩子结点不满足堆的性质,所以每次交换之后需要重新对被交换的孩子结点进行调整。
总结:堆排序就是 先从 最后非叶子节点 往前调整堆以 建堆, 然后 最后一个元素和堆顶交换 后 ,再从 顶往后调整堆, 核心还是调整堆.
时间复杂度:0(nlogn)
要进行升序需要建立大根堆,降序需要建立小根堆.(和最后交换)
java实现:
堆调整
import java.util.*;
public class HeapSort {
/**
* 从顶往下调整,比较和较大的子节点比大小
*
* @param arr
* @param head
* @param end
*/
public static void heapAdjust(int arr[], int head, int end) {
int temp = arr[head];
// 直接将i移到子节点,i每次*2到下个头结点!!
for (int i = 2 * head + 1; i < end; i *= 2) {
// 比较左右子节点的大小
if (i < end && arr[i] < arr[i + 1]) {
i++;
}
if (temp > arr[i]) { // 比较大子节点和顶部的大小
break;
}
arr[head] = arr[i];
head = i;// 继续从新的head往下比较
}
arr[head] = temp; // 放置最开适不和谐的元素
}
public static void heapSort(int arr[]) {
// 从最后的叶子节点往前建堆
for (int i = arr.length / 2; i >= 0; i--) {
//!! 注意子节点+1了,不要越界
heapAdjust(arr, i, arr.length );
}
System.out.println("创建堆后");
for (int i = 0; i < arr.length; i++) {
System.out.print(" " + arr[i] + " ");
}
// 交换无序区的最后一个元素和顶元素
for (int i = arr.length-1; i >1; i--) {//i>1否则会多排一次0,1会交换,开始以为少了一次
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// 将0-i-1的无序区调整成堆
heapAdjust(arr, 0, i -1);
}
}
public static void main(String args[]) {
int[] arr = new int[] { 70, 60, 12, 40, 30, 8, 10 };
heapSort(arr);
System.out.println("");
System.out.println("排序后");
for (int i = 0; i < arr.length; i++) {
System.out.print(" " + arr[i] + " ");
}
}
}
参考:http://www.cnblogs.com/mengdd/archive/2012/11/30/2796845.html
参考:http://blog.csdn.net/morewindows/article/details/6709644/
插入排序
直接插入排序
插入排序是复杂度为O(n2)排序中效率最高的,一般比冒泡快一倍,比选择快一些,但不及快速排序,所以对于简单排序优先选择插入排序。
思想:左边为已排序区,右边为未排序区,从右边拿出第一个数插入到左边的适当位置,插入时从排序区右边开始向左遍历,每次比较如果待插值小于当前值,将当前值右移,空出位置放待插值。
实现:外层循环未排序区第一个数开始,内层循环排序区最后一个数也是未排序区的第一个数往左遍历同时将数往右移动。
java实现:
/**
* 插入排序
* @param arr
*/
public static void insertSort(int arr[]) {
for (int out = 1; out < arr.length; out++) {
int inner = out;
int pivot = arr[out];//必须将待插值保存,否则会被覆盖
while (inner > 0 && pivot <= arr[inner-1]) {//比较和移动有序区
arr[inner ] = arr[inner-1];
inner--;
}
arr[inner ] = pivot;
}
}
希尔排序
希尔排序是优化的插入排序,简单的插入排序每次插入需要按间隔为 1 移动有序区的数,效率不高,希尔排序将优化了这一过程,通过 一定的间隔 对数据进行分组,组内进行排序,逐渐减小间隔,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的。
java实现:
/**
* 多了两层循环,一层控制gap>0,一层控制分组
* @param arr
*/
public static void shellSort(int[] arr) {
int gap = arr.length / 2;
while (gap > 0) {// 逐渐减小gap,最中为1,进行直接插入排序
for (int i = 0; i < gap; i++) {// 排序第i组,每一组内部进行插入排序
for (int out = gap + i; out < arr.length; out += gap) {// 直接插入排序的间隔1变为gap即可
int inner = out;
int pivot = arr[out];
while (inner > gap-1 && pivot <= arr[inner - gap]) {//inner > gap -1 ,
arr[inner] = arr[inner - gap];
inner -= gap;
}
arr[inner] = pivot;//放置pivot
}
}
gap /= 2;
}
}
参考:http://blog.csdn.net/morewindows/article/details/6668714
归并排序
归并算法,采用分治思想,
先看合并两个有序数组,只需要比较两个数组中的数大小,选出较小放在打的前面,循环两遍就可以得到合并结果,
归并就是先将数组递归分解,直到只有一个数时,认为数组是有序的。进行合并。
例如:
()
归并需要一个临时数组进行保存合并的结果。
java实现:
/**
* 合并数组
*
* @param arr
* @param start
* @param mid
* @param end
*/
public static void merge(int arr[], int start, int mid, int end) {
int leftIndex = start;
int rightIndex = mid;
int tempIndex = 0;
int [] temp = new int[end-start];
while (leftIndex < mid && rightIndex < end) {
if (arr[leftIndex] < arr[rightIndex]) {
temp[tempIndex++] = arr[leftIndex++];// 把++放到这
} else {
temp[tempIndex++] = arr[rightIndex++];
}
}
while (leftIndex < mid) {
temp[tempIndex++] = arr[leftIndex++];// 注意left++
}
while (rightIndex < end) {
temp[tempIndex++] = arr[rightIndex++];
}
for (int i = 0; i < tempIndex; i++) {//赋值不是全部从头到尾,而是在0--tempIndex!!
arr[start+i] = temp[i];
}
System.out.println("合并"+"start "+start+" end "+end +" 后: temp");
for (int i = 0; i < temp.length; i++) {
System.out.print(" " + temp[i]);
}
}
/**
* 归并排序
*
* @param arr
* @param start
* @param end
* @param temp
*/
public static void mergeSort(int[] arr, int start, int end) {
if (start +1 < end) {//结束的判断,这里卡了start < end,至少有三个数才分解,成为0-1-2
int middle = (start + end) / 2;
System.out.println();
System.out.println("分解" + "start: " + start + " middle: " + (middle ));
mergeSort(arr, start, middle );//划分不需要-1
System.out.println();
System.out.println( "分解" + "middle : " + (middle ) + " end: " + end);
mergeSort(arr, middle , end);//middle 不需要+1
merge(arr, start, middle, end);
System.out.println();
}
}
非递归实现:http://www.cnblogs.com/jingmoxukong/p/4308823.html
分配排序
基数排序
http://www.cnblogs.com/Braveliu/archive/2013/01/21/2870201.html
基数排序的基数是指 ,数的进制,例如十进制数,先建立十个桶,按数的每一位上的数字放入到不同的桶中,再取出放回到数组中,这时数组中的数是按某一位上的数有序的,当把数的所有位都进行这样的处理之后,从桶中取出,数组就是有序的。
这是一个分配和收集的过程。
时间复杂度O(n),但大部分情况都是O(nlogn)
实现:使用LinkedList实现简单一点+_+||
1.确定数中最长位数,确定分配和收集的循环次数
2.一个链表创建10个子桶
3.分配:求数组中的每个数的各位上的值,根据值将该数放入到对应桶中,
4.收集:从子桶中取出数,放会数组
数组实现桶:http://blog.csdn.net/apei830/article/details/6596104
使用数组实现桶要复杂一点:
实现步骤:
1.创建两个临时数组,一个用于存放一次排序后的结果temp,一个大小为桶个数count,统计每个桶的大小,用于转换成temp数组的下标左右范围。
2.统计每个桶的大小,即计算相同位上数字出现次数。
3.确定桶在temp数组中下标的左右范围
4.从后往前根据下标将数放入桶内。
5.收集temp中的数到原数组
使用LinkedList 实现桶:http://blog.csdn.net/pzhtpf/article/details/7560312
java实现:
/**
* 使用list实现的基数排序
*
* @param arr
*/
public static void radixSort(int[] arr) {
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (max < arr[i])
max = arr[i];
}
int bits = 0;
while (max> 0) {
max=(int) (max / 10);
bits++;
}
List<List<Integer>> buckets = new ArrayList<>();
for (int i = 0; i < 10; i++) {// 创建桶
buckets.add(i, new LinkedList<Integer>());
}
for (int bit = 0; bit < bits; bit++) {// 进行多少次分配收集
for (int i = 0; i < 10; i++) {// 每次清空各个桶
buckets.get(i).clear();
}
for (int i = 0; i < arr.length; i++) {// 按位上的数字分配到不同的桶
buckets.get(getBit(arr[i], bit)).add(arr[i]);
}
System.out.println();
System.out.println("第" + bit + "次分配");
System.out.println();
System.out.println(buckets.toString());
System.out.println();
System.out.println("第" + bit + "次收集");
int k = 0;
for (int i = 0; i < 10; i++) {// 收集,将桶内的数收集到数组中
for (int j = 0; j < buckets.get(i).size(); j++) {
arr[k] = buckets.get(i).get(j);
System.out.print(" " + arr[k] + " ");
k++;//坑++!!
}
}
}
}
/**
* 获取每位的数
*
* @param num
* @param bit
* @return
*/
public static int getBit(int num, int bit) {
return (int) (num / Math.pow(10, bit) % 10);
}
计数排序
计数排序适用于,数的大小范围不是很大的情况,而数据量较大的情况,时间复杂可以达到O(n).
可以用于基数排序中
实现思路:
1.统计数组的最大最小值,确定计数范围
2.统计每个数出现的次数,放入数组count的位置为 value-min
3.写回到原数组,位置为count[i]+min,count[i]到0位置
java实现:
/**
* 计数排序
* @param arr
*/
public static void countSort(int arr[]) {
int min = arr[0], max = arr[0];
for (int i = 0; i < arr.length; i++) {//统计最大最小
if (arr[i] < min) {
min = arr[i];
}
if (arr[i] > max) {
max = arr[i];
}
}
int[] count = new int[max - min + 1];
for (int i = 0; i < arr.length; i++) {//统计数字出现的次数
count[arr[i] - min]++;
}
int index=0;
for (int i = 0; i < count.length; i++) {//根据count的值填充原数组,
while (count[i]-- > 0) {
arr[index++] = i + min;
}
}
}
参考:http://www.cnblogs.com/eaglet/archive/2010/09/16/1828016.html
桶排序
桶排序将数据通过hash散列分成多个组,将分好的数放在不同桶里,然后对每个桶内部进行排序,使用前面几种排序方式或者递归的对桶内进行桶排序。
将大量数据变成基本有序,在进行排序,提高效率。
桶必须是有序的,前面的桶必须小于后面的桶的数据。
时间复杂度:O(n)
使用链表维护桶的大小和先后顺序,桶内的元素也使用链表进行排序,可以使用直接插入排序。
实现:
1.创建n个桶,
2.根据hash,向桶内赋值,同时排序
3.将桶内值收集到数组。
java代码:
//桶内元素
static class Element {
int value;
Element next;
public Element(int value) {
this.value = value;
}
}
//桶节点
static class Bucket {
Element head;
int size;
}
//桶排序
public static void bucketSort(int arr[]) {
Bucket[] buckets = new Bucket[arr.length];
for (int i = 0; i < buckets.length; i++) {// 初始化桶
buckets[i] = new Bucket();
buckets[i].head = null;
buckets[i].size = 0;
}
createBuckets(arr, buckets);// 将数值分配到桶内
reverseEvalution(arr, buckets);// 将桶内值收集到数组
}
//将桶内元素赋值给数组
private static void reverseEvalution(int[] arr, Bucket[] bucktes) {
int i = 0;
for (int j = 0; j < bucktes.length; j++) {
if (bucktes[j].size > 0) {
Element elem = bucktes[j].head;
while (elem != null) {
arr[i++] = elem.value;
elem = elem.next;
}
}
}
}
//创建桶
private static void createBuckets(int[] arr, Bucket[] buckets) {
for (int i = 0; i < arr.length; i++) {
int index = hash(arr[i]);
System.out.println("插入" + arr[i] + "到桶" + index);
insertSort(buckets[index], arr[i]);
}
}
//桶内使用直接插入排序创建有序链表
private static void insertSort(Bucket bucket, int i) {
Element in = new Element(i);
in.next = null;
Element current = null;
Element previous = null;
current = bucket.head;
if (current==null || current.value > in.value) {// 头部插入
bucket.head = in;
in.next = current;//!!注意current不为null时next要赋值
bucket.size++;
} else {//不在头部插入
while (current != null) {
if (current.value <= in.value) {//当前小于in
previous = current;
current = current.next;
} else {//in 在中间某个位置插入
previous.next = in;
in.next = current;
bucket.size++;
break;
}
}
//in比前面都大将in插入最后,尾部插入
previous.next = in;
bucket.size++;
}
}
//将元素分桶的hash函数
public static int hash(int elem) {
return elem / 3;
}
总结
排序算法时间复杂度和空间复杂度比较表