排序算法稳定性:
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
对以下算法的描述及实现均是针对以升序排序而言的。
1、冒泡排序(稳定排序)
基本思想:通过对待排序序列从前往后(从下标为0的元素开始),通过依次比较相邻元素的值,若下标较小的元素的值较大则交换,则值较大的元素会逐渐从前向后移动。
时间复杂度:平均和最坏时间复杂度都是o(n2)
(1)冒泡排序的基本实现
/**
* 冒泡排序
* @param arr
*/
public static void bubbleSort(int[] arr) {
int temp=0;
for (int i = 0; i <arr.length-1; i++) {
for (int j = 0; j <arr.length-i-1; j++) {
if(arr[j]>arr[j+1]){//若数组下标较小的元素的值较大
//交换数组中这两个元素的值。
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
(2)冒泡排序的优化实现
/**
* 优化的冒泡排序
* @param arr
*/
public static void optimizeBubbleSort(int[] arr) {
int temp=0;
boolean isOrder=false;//用来判断数组是否有序的标志
for (int i = 0; i <arr.length-1; i++) {
isOrder=true;
for (int j = 0; j <arr.length-i-1; j++) {
if(arr[j]>arr[j+1]){//若数组下标较小的元素的值较大
//交换数组中这两个元素的值。
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
//数组仍然无序
isOrder=false;
}
}
if(isOrder)//若数组已然有序,则结束排序
break;
}
}
2、选择排序(不稳定排序)
基本思想:第一次从待排序的数据元素中选出最小的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。
时间复杂度:平均和最坏时间复杂度都是o(n2)
①普通选择排序:
/**
* 选择排序
* @param arr
*/
public static void selectSort(int[] arr){
int temp=0;//用来暂存元素,便于交换数组中两个元素的位置
int min=0;//从数组无序序列中选取出最小的数
int indexOfMin=0;//记录数组无序序列中最小的数对应的数组下标
for (int i = 0; i <arr.length; i++) {
min=arr[i];
indexOfMin=i;
for (int j =i+1; j <arr.length; j++) {
if(min>arr[j]) {
min=arr[j];
indexOfMin=j;
}
}
//交换这两个元素在数组中的位置
temp=arr[i];
arr[i]=arr[indexOfMin];//arr[indexOfMin]的值等于min
arr[indexOfMin]=temp;
}
}
②及时终止的选择排序:(有点奇怪的是在我的机器上跑了多次上万条数据,及时终止的选择排序还不如普通选择排序快)
/**
* 及时终止的选择排序
* @param arr
*/
public static void selectSort(int[] arr){
int temp=0;//用来暂存元素,便于交换数组中两个元素的位置
int indexOfMax=0;//记录数组无序序列中最大的数对应的数组下标
boolean isSorted=false;//记录数组是否有序
for (int i =arr.length;!isSorted&&i>1; i--) {
indexOfMax=0;
isSorted=true;
for (int j =1; j <i; j++) {
if(!(arr[indexOfMax]>arr[j])) {
indexOfMax=j;
}else
isSorted=false;//无序
}
//交换这两个元素在数组中的位置
temp=arr[i-1];
arr[i-1]=arr[indexOfMax];
arr[indexOfMax]=temp;
}
}
3、插入排序(稳定排序)
基本思想:插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据
时间复杂度:平均和最坏时间复杂度都是o(n2)
①交换方式实现的插入排序:
/**
* 插入排序
* @param arr
*/
public static void insertSort(int[] arr){
int temp=0;//用来暂存元素,便于交换数组中两个元素的位置
for (int i = 1; i <arr.length; i++) {
for (int j = i; j>0; j--) {
if(arr[j]<arr[j-1]){
//交换两个元素的位置
temp=arr[j];
arr[j]=arr[j-1];
arr[j-1]=temp;
}
}
}
}
②移位方式实现的插入排序(相比于交换方式,更快)
/**
* 插入排序
* @param arr
*/
public static void insertSort(int[] arr){
int temp=0;//用来暂存元素
for (int i = 1; i <arr.length; i++) {
temp=arr[i];
int j;
for (j =i-1; j>=0&&temp<arr[j]; j--)
arr[j+1]=arr[j];
arr[j+1]=temp;
}
}
4、希尔(Shell)排序(不稳定排序)
希尔排序是插入排序的一种又称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。
基本思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量 =1(dt<dt-1…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
时间复杂度:
使用希尔增量时希尔排序的最坏情形运行时间为o(n2)。
使用Hibbard增量的希尔排序的最坏情形运行时间为o(n3/2)(Hibbard增量序列:1,4,7,…,2k-1。这个增量的特点是增量没有公因子)
①交换方式实现的希尔排序(比插入排序还慢):
/**
* 希尔排序
* @param arr
*/
public static void shellSort(int[] arr){
int temp=0;
//分组进行排序,增量d,并逐步缩小增量
for (int d =arr.length/2; d>0; d/=2) {
for (int i =d; i <arr.length; i++) {
//遍历各组中的所有元素,共group组,步长group
for (int j =i-d; j>=0; j-=d) {
//如果当前元素大于加上步长后的元素,交换
if(arr[j]>arr[j+d]){
temp=arr[j];
arr[j]=arr[j+d];
arr[j+d]=temp;
}
}
}
}
}
②移位方式实现的希尔排序:(比交换方式和插入排序更快得多)
/**
* 希尔排序
* @param arr
*/
public static void shellSort(int[] arr){
int temp=0;
//分组进行排序,增量d,并逐步缩小增量
for (int d =arr.length/2; d>0; d/=2) {
for (int i =d; i <arr.length; i++) {
int j=i;
temp=arr[j];
if(arr[j]<arr[j-d]){
while(j-d>=0&&temp<arr[j-d]){
//移动
arr[j]=arr[j-d];
j-=d;
}
//退出while循环后就找到了合适的插入位置
arr[j]=temp;
}
}
}
}
5、快速排序(不稳定排序)
基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
时间复杂度:平均时间复杂度是o(nlogn),最坏时间复杂度是o(n2)
/**
* 快速排序
* @param arr
* @param start
* @param end
*/
public static void quickSort(int[] arr, int start, int end) {
int temp=0;//用来交换两个数组元素时暂存数据
int left=start;//左下标
int right=end;//右下标
int pivot=arr[(left+right)/2];//中轴值
//将所有比pivot值小元素的放在左边,比pivot大的元素放在右边
while(left<right){
//在左边找到一个比pivot大的值才退出
while (arr[left]<pivot)
left++;
//在右边找到一个比pivot小的值才退出
while (arr[right]>pivot)
right--;
//如果条件成立,说明左边全是小于等于pivot的数,右边全是大于等于pivot的数
if(left>=right)
break;
//交换
temp=arr[left];
arr[left]=arr[right];
arr[right]=temp;
/*
if(arr[left]==pivot)
right--;
if(arr[right]==pivot)
left++;*/
}
if(left==right) {
left++;
right--;
}
//向左递归
if(start<right)
quickSort(arr,start,right);
//向右递归
if(end>left)
quickSort(arr,left,end);
}
6、归并排序(稳定排序)
基本思想:将已有序的子序列合并,得到完全有序的序列。
时间复杂度:平均和最坏时间复杂度都是o(nlogn)
空间复杂度:o(n)(需要一个辅助数组)
/**
* 归并排序
* @param arr 原始数组
* @param temp 暂存数据的数组
* @param start
* @param end
*/
public static void mergeSort(int[] arr,int[] temp,int start,int end){
if(start<end){
int middle=(start+end)/2;
//向左递归进行分解
mergeSort(arr,temp,start,middle);
//向递归进行分解
mergeSort(arr,temp,middle+1,end);
//归并
merge(arr,temp,start,middle,end);
}
}
/**
* 将左右分别有序的数组进行归并
* @param arr
* @param temp
* @param start
* @param middle
* @param end
*/
private static void merge(int[]arr,int[] temp,int start,int middle,int end){
int indexOfLeft=start;//初始化指向数组左边有序序列索引
int indexOfRight=middle+1;//初始化指向数组右边有序序列索引
int indexOfTemp=start;//初始化指向temp数组的索引
//将左右两边数组的值按照升序顺序暂存在temp数组中
//直到有一边数组中的全部元素都暂存在temp中了
while (indexOfLeft<=middle&&indexOfRight<=end){
//如果右边的数比较小
if(arr[indexOfRight]<arr[indexOfLeft]){
temp[indexOfTemp++]=arr[indexOfRight++];
}else {
temp[indexOfTemp++]=arr[indexOfLeft++];
}
}
//将还未处理完毕的一边的数组的全部元素直接复制到temp数组中
while (indexOfLeft<=middle){
temp[indexOfTemp++]=arr[indexOfLeft++];
}
while (indexOfRight<=end){
temp[indexOfTemp++]=arr[indexOfRight++];
}
//将temp数组中的结果拷贝到原数组
for (int i =start; i <=end; i++) {
arr[i]=temp[i];
}
}
7、基数排序(稳定排序)
温馨提示:我的基数排序实现无法对包含负数的数组进行排序,否则会出现ArrayIndexOutOfBoundsException异常。
/**
* 基数排序
* @param arr
*/
public static void radixSort(int[] arr){
int d=maxBit(arr);
int[] temp = new int[arr.length];
int count[]=new int[10]; //计数器
int i, j, k;
int radix = 1;
for(i = 1; i <= d; i++) //进行d次排序
{
for(j = 0; j < 10; j++)
count[j] = 0; //每次分配前清空计数器
for(j = 0; j <arr.length; j++)
{
k = (arr[j] / radix) % 10; //统计每个桶中的记录数
count[k]++;
}
for(j = 1; j < 10; j++)
count[j] = count[j - 1] + count[j]; //将temp中的位置依次分配给每个桶
for(j = arr.length - 1; j >= 0; j--) //将所有桶中记录依次收集到temp中
{
k = (arr[j] / radix) % 10;
temp[count[k] - 1] = arr[j];
count[k]--;
}
for(j = 0; j <arr.length; j++) //将临时数组的内容复制到arr中
arr[j] = temp[j];
radix = radix * 10;
}
}
/**
* 返回数组中最大位数(个位数,十位数,百位数....)
* @param arr
* @return
*/
private static int maxBit(int[] arr){
int max=arr[0];
int min=arr[0];
for (int i = 1; i <arr.length; i++) {
if(max<arr[i])
max=arr[i];
if(min>arr[i])
min=arr[i];
}
//获取最大位数
int maxLength=(max+"").length();
return maxLength;
}
8、堆排序(不稳定排序)
在堆的数据结构中,堆中的最小值(小根堆)总是位于根节点,每次获取堆的根节点保存在数组中(索引逐渐增加),并将堆的根节点删除。这样数组中的序列就是有序的。
时间复杂度:平均和最坏时间复杂度都是o(nlogn)
/**
* 堆排序
* @param arr
*/
public static void heapSort(int[] arr){
PriorityQueue<Integer> minHeap=new PriorityQueue<>();
for (int i = 0; i <arr.length; i++) {
minHeap.add(arr[i]);
}
for (int i = 0; i <arr.length; i++) {
//将堆的根节点删除,并保存在数组中。
arr[i]=minHeap.poll();
}
}