常见的几种排序算法
冒泡法
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
1、比较相邻的元素如果第一个比第二个大,就交换它们两个。
2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
3、针对所有的元素重复以上的步骤,除了最后一个;
4、重复步骤1〜3,直到排序完成。
private static void bubbleSort(int[] a) {
int len = a.length;
int temp;
for (int i = 0; i < len; i++) {
for (int j = 0; j < len - i - 1; j++) {
if (a[j] > a[j + 1]) {//相邻两个元素两两比较
temp = a[j + 1];//元素交换
a[j + 1] = a[j];
a[j] = temp;
}
}
}
}
选择排序算法
选择排序(选择排序)是一种简单直观的排序算法它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
1、初始状态:无序区为[R [1…N],有序区为空;
2、第i趟排序(I = 1,2,3,… N-1)开始时,当前有序区和无序区分别为[R [1…i-1]和R(i…n)。
该趟排序从当前无序区中 - 选择关键字最小的记录R [k],将它与无序区的第1个记录R交换,
使R [1…i]和R [i + 1 … n)的分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
3、n-1个趟结束,数组有序化了。
private static void selectionSort(int[] a) {
int len = a.length;
int minIndex, temp;
for (int i = 0; i < len - 1; i++) {
minIndex = i;
for (int j = i + 1; j < len; j++) {
if (a[j] < a[minIndex]) {//寻找最小数
minIndex = j;//将最小数的索引号保存
}
}
temp = a[i];
a[i] = a[minIndex];
a[minIndex] = temp;
}
}
插入排序算法
插入排序(插入分页)的算法描述是一种简单直观的排序算法。
它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
1、从第一个元素开始,该元素可以认为已经被排序;
2、取出下一个元素,在已经排序的元素序列中从后向前扫描;
3、如果该元素(已排序)大于新元素,将该元素移到下一位置;
4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
5、将新元素插入到该位置后;
6、重复步骤2〜5。
public static void insertionSort(int[] a) {
int len = a.length;
int preIndex, current;
for (int i = 0; i < len; i++) {
preIndex = i - 1;
current = a[i];
while (preIndex >= 0 && a[preIndex] > current) {
a[preIndex + 1] = a[preIndex];
preIndex--;
}
a[preIndex + 1] = current;
}
}
希尔排序
希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。它与插入排序的不同之处在于,它会优先比较距离较远的元素。
希尔排序又叫缩小增量排序。希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
1、选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2、按增量序列个数k,对序列进行k 趟排序;
3、每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
public static void shellSort(int[] a) {
int len = a.length;
int temp, gap = len / 2; //gap:定义的增量初始值为len/2,意味着整个序列将会被分为gap组
while (gap > 0) {
for (int i = gap; i < len; i++) {
temp = a[i];
int preIndex = i - gap;
while (preIndex >= 0 && a[preIndex] > temp) {
a[preIndex + gap] = a[preIndex];
preIndex -= gap;
}
a[preIndex + gap] = temp;
}
gap /= 2;//继续缩小增量
}
}
归并排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
若将两个有序表合并成一个有序表,称为2-路归并。
1、把长度为n的输入序列分成两个长度为n/2的子序列;
2、对这两个子序列分别采用归并排序;
3、将两个排序好的子序列合并成一个最终的排序序列。
public static void mergeSort(int[] a, int start, int end) {
if (start < end) {//当子序列中只有一个元素的时候结束递归
int mid = (start + end) / 2;//划分子序列
mergeSort(a, start, mid);//对左侧子序列进行递归排序
mergeSort(a, mid + 1, end);//对右侧子序列进行递归排序
merge(a, start, mid, end);//合并
}
}
private static void merge(int[] a, int start, int mid, int end) {
int[] result = new int[a.length];
int p1 = start, p2 = mid + 1, k = start;//p1,p2是检测指针,k是存放指针
while (p1 <= mid && p2 <= end) {
if (a[p1] <= a[p2]) {
result[k++] = a[p1++];
} else {
result[k++] = a[p2++];
}
}
while (p1 <= mid) {//如果第一个序列未检测完,直接将后面所有元素加到合并的序列中
result[k++] = a[p1++];
}
while (p2 <= end) {//如果第二个序列未检测完,直接将后面的所有元素加到合并的序列中
result[k++] = a[p2++];
}
//复制回原数组
for (int i = start; i <= end; i++) {
a[i] = result[i];
}
}
快速排序
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
1、从数列中挑出一个元素,称为“基准”(pivot);
2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置这个称为分区(分区)操作。
3、递归地(递归)把小于基准值元素的子数列和大于基准值元素的子数列排序。
public static void quiksort(int[] a, int low, int high) {
int i, j, index;
if (low > high) {
return;
}
i = low;
j = high;
index = a[i];
while (j > i) {
while (i < j && a[j] >= index) {
j--;
}
if (i < j) {//如果元素比基准小,则将该元素放到基准的前面
a[i++] = a[j];
}
while (i < j && a[i] < index) {
i++;
}
if (i < j) {
a[j--] = a[i];
}
}
a[i] = index;
quiksort(a, low, i - 1);
quiksort(a, i + 1, high);
}
堆排序
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
private static void heapSort(int[] a) {
//构建大顶堆
for(int i=a.length/2-1;i>=0;i--){
//从第一个非叶子节点从上至下,从左至右调整结构
adjustHeap(a,i,a.length);
}
//调整堆结构,并且交换堆顶元素与末尾元素
for(int j=a.length-1;j>0;j--){
swap(a,0,j);//将堆顶元素与末尾元素进行交换
adjustHeap(a,0,j);//重新对堆进行调整
}
}
private static void swap(int[] a, int i, int j) {
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
private static void adjustHeap(int[] a, int i, int length) {
int temp=a[i];//先取出当前元素i
for(int k=i*2+1;k<length;k=k*2+1){//从i节点的左子节点开始,也就是2i+1处开始
if(k+1<length && a[k]<a[k+1]){//如果左子节点小于右子节点,k指向右子节点
k++;
}
if(a[k]>temp){//如果子节点大于父节点,将子节点赋值给父节点(不用进行交换)
a[i]=a[k];
i=k;
}else {
break;
}
}
a[i]=temp;//将temp值放到最终位置
}
基数排序
基数排序:计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。
作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。
1、找出待排序的数组中最大和最小的元素;
2、统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
3、对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
4、反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
public static void countingSort(int[] a){
int bias,min=a[0],max=a[0];
for(int i=0;i<a.length;i++){
if(a[i]>max){
max=a[i];
}
if(a[i]<min){
min=a[i];
}
}
bias=0-min;
int[] bucket=new int[max-min+1];
Arrays.fill(bucket,0);
for(int i=0;i<a.length;i++){
bucket[a[i]+bias]++;
}
int index=0,i=0;
while (index<a.length){
if(bucket[i]!=0){
a[index]=i-bias;
bucket[i]--;
index++;
}else {
i++;
}
}
}
总结
总结:
* 一、稳定性:
* 稳定:冒泡排序、插入排序、归并排序和基数排序
* 不稳定:选择排序、快速排序、希尔排序、堆排序
* 二、时间复杂度:
* O(n^2):直接插入排序,简单选择排序,冒泡排序。
* 在数据规模较小时(9W内),直接插入排序,简单选择排序差不多。当数据较大时,冒泡排序算法的时间代价最高。
* 性能为O(n^2)的算法基本上是相邻元素进行比较,基本上都是稳定的。
* O(nlogn):快速排序,归并排序,希尔排序,堆排序。
* 其中,快排是最好的, 其次是归并和希尔,堆排序在数据量很大时效果明显。
* 三、排序算法的选择
* 1、数据规模较小
* (1)待排序列基本序的情况下,可以选择直接插入排序;
* (2)对稳定性不作要求宜用简单选择排序,对稳定性有要求宜用插入或冒泡
* 2、数据规模不是很大
* (1)完全可以用内存空间,序列杂乱无序,对稳定性没有要求,快速排序,此时要付出log(N)的额外空间。
* (2)序列本身可能有序,对稳定性有要求,空间允许下,宜用归并排序
* 3、数据规模很大
* (1)对稳定性有求,则可考虑归并排序。
* (2)对稳定性没要求,宜用堆排序
* 4、序列初始基本有序(正序),宜用直接插入,冒泡
*
* 各算法的复杂度如下:
* 排序算法 最差时间 平均时间复杂度 最好时间 稳定度 空间复杂度 备注
* 选择排序 O(n^2) O(n^2) O(n^2) 不稳定 O(1) n小时较好
* 插入排序 O(n^2) O(n^2) O(n) 稳定 O(1) 大部分数据已有序时好
* 冒泡排序 O(n^2) O(n^2) O(n) 稳定 O(1) n小时较好
* 快速排序 O(n^2) O(nlogn) O(nlogn) 不稳定 O(logn) n大时比较好
* 归并排序 O(nlogn) O(nlogn) O(nlogn) 稳定 O(n) n大时较好
* 希尔排序 O(n^s) O(n^2) O(n) 不稳定 O(1) s是所选分组
* 堆排序 O(nlogn) O(nlogn) O(nlogn) 不稳定 O(1) n大时比较好