各排序算法的时间复杂度、空间复杂度及稳定性
冒泡排序
排序思想
就像“冒泡泡”一样,从头开始,两个两个数作比较,将两个数中较大(较小)的数向后移,再比较下面两个数,依次将较大(较小)的数向后移,最终,将一组数中最大(最小)的数,移动到最后面,再从头开始,移下一个数
下面是一个栗子:
要排序数组(升序):{4,2,7,3,1}
————————————
第一趟(交换四次):
- 第一次:4与2比较,4大于2,交换两个数——{2,4,7,3,1}
- 第二次:4与7比较,7大于4,不交换位置——{2,4,7,3,1}
- 第三次:7与3比较,7大于3,交换两个数——{2,4,3,7,1}
- 第四次:7与1比较,7大于1,交换两个数——{2,4,3,1,7}
第二趟(交换3次):
- 第一次:2与4比较,4大于2,不交换位置——{2,4,3,1,7}
- 第二次:4与3比较,4大于3,交换两个数——{2,3,4,1,7}
- 第三次:4与1比较,4大于1,交换两个数——{2,3,1,4,7}
第三趟(交换两次):
- 第一次:2与3比较,3大于2,不交换位置——{2,3,1,4,7}
- 第二次:3与1比较,3大于1,交换两个数——{2,1,3,4,7}
第四趟(交换一次):
- 第一次:2与1比较,2大于1,交换两个数——{1,2,3,4,7}
————————————
根据上面的栗子可以得出有n个数字要进行排序总共要进行 n - 1 趟,每 i 趟要排序 n - i - 1 次
排序优点
每进行一趟排序,就会少比较一次,因为每进行一趟排序都会找出一个较大值。如上例:第一趟比较之后,排在最后的一个数一定是最大的一个数,第二趟排序的时候,只需要比较除了最后一个数以外的其他的数,同样也能找出一个最大的数排在参与第二趟比较的数后面,第三趟比较的时候,只需要比较除了最后两个数以外的其他的数,以此类推……也就是说,每进行一趟比较,每一趟少比较一次,一定程度上减少了算法的量。
时间复杂度
冒泡排序的时间复杂度为O(N^2)
空间复杂度
冒泡排序的空间复杂度为O(1)
实现代码
/**
* 冒泡排序(升序)
* @author LXY
* @email 403824215@qq.com
* @date 2018/8/18 16:10
*/
public class BubbleSort {
public static void bubbleSort(int[] arr)
{
if(arr == null || arr.length == 0)
{
return;
}
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])
{
swap(arr,j,j + 1);
}
}
}
}
private static void swap(int[] arr, int j, int i) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void main(String[] args) {
int[] arr = {4,2,7,3,1,8,9,5};
bubbleSort(arr);
for(int i:arr)
{
System.out.print(i + " ");
}
}
}
选择排序
排序思想
每一趟从待排序的记录中选出最小的元素,顺序放在已排好序的序列最后,直到全部记录排序完毕。也就是:每一趟在n-i+1(i=1,2,…n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。
下面是一个栗子:
要排序数组(升序):{4,2,7,3,1}
————————————
第一趟:
- 将数组中最小的元素1与第一个元素4交换——{1,2,7,3,4}
第二趟:
- 将除了第一个元素外的最小元素2,与第二个位置的元素2交换(此处就是2,因此不用交换)——{1,2,7,3,4}
第三趟:
- 将除了前两个元素外的最小元素3与第三个位置的元素7交换——{1,2,3,7,4}
第四趟:
- 将除了前三个元素外的最小元素4与第四个位置的元素7交换——{1,2,3,4,7}
————————————
时间复杂度
选择排序的时间复杂度为O(N^2)
空间复杂度
选择排序的空间复杂度为O(1)
代码实现
package selectSort;
/**
* 选择排序(升序)
* @author LXY
* @email 403824215@qq.com
* @date 2018/8/18 16:31
*/
public class SelectSort {
public static void selectSort(int[] arr)
{
if(arr == null || arr.length == 0)
{
return;
}
for(int i = 0;i < arr.length - 1;i++)
{
int index = i;
for(int j = i + 1;j < arr.length;j++)
{
if(arr[j] < arr[index])
{
swap(arr,j,index);
}
}
}
}
private static void swap(int[] arr, int j, int i) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void main(String[] args) {
int[] arr = {4,2,7,3,1,8,9,5};
selectSort(arr);
for(int i:arr)
{
System.out.print(i + " ");
}
}
}
插入排序
排序思想
插入排序就像是玩扑克牌时要把牌放到手中以有的牌中一样,像下面这张图一样,原本手中的牌是{2,4,5,10},但是抽到了一张7,此时应该怎么办?
将牌从右向左看,7比10小,比5大,那么正好就放到这里,那么为什么不继续向前面比较了?是因为牌本身是有顺序的
图片是盗的
下面是一个情况最坏的栗子:
要排序数组(升序):{5,4,3,2}
————————————
第一次插入:
- 将位置1(数组从位置0开始算起,并且默认位置0,即第一个元素已经插入完成)的元素4开始与它之前的元素5进行比较,发现4比5小,将4和5交换{4,5,3,2}
第二次插入:
- 将位置2的元素3与它之前的元素5进行比较,发现3比5小,将3和5交换{4,3,5,2}
- 将位置1的元素3与它之前的元素4进行比较,发现3比4小,将3和4交换{3,4,5,2}
第三次插入:
- 将位置3的元素2与它之前的元素5进行比较,发现2比5小,将2和5交换{3,4,2,5}
- 将位置2的元素2与它之前的元素4进行比较,发现2比4小,将3和5交换{3,2,4,5}
- 将位置1的元素2与它之前的元素3进行比较,发现2比3小,将2和3交换{2,3,4,5}
————————————
时间复杂度
插入排序的时间复杂度为O(N^2)
空间复杂度
插入排序的空间复杂度为O(1)
代码实现
package insertSort;
/**
* 插入排序(升序)
* 该种方法需要不断地进行交换赋值,这样会产生时间地浪费
* @author LXY
* @email 403824215@qq.com
* @date 2018/8/18 16:48
*/
public class InsertSort {
public static void insertSort(int[] arr)
{
if(arr == null || arr.length == 0)
{
return;
}
//默认第一个位置的元素摆放完毕,从第二个位置的元素开始
for(int i = 1;i < arr.length;i++)
{
for(int j = i;j > 0 && arr[j] < arr[j - 1];j--)
{
swap(arr,j,j - 1);
}
}
}
private static void swap(int[] arr, int j, int i) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void main(String[] args) {
int[] arr = {4,2,7,3,1,8,9,5};
insertSort(arr);
for(int i:arr)
{
System.out.print(i + " ");
}
}
}
package insertSort;
/**
* 插入排序(升序)
* 该种方法是上面那种方法地改进版本,不需要来回地交换元素
* @author LXY
* @email 403824215@qq.com
* @date 2018/8/18 16:48
*/
public class InsertSort2 {
public static void insertSort(int[] arr)
{
if(arr == null || arr.length == 0)
{
return;
}
//默认第一个位置的元素摆放完毕,从第二个位置的元素开始
for(int i = 1;i < arr.length;i++)
{
int j;
int tmp = arr[i];
for(j = i;j > 0 && arr[j - 1] > tmp;j--)
{
arr[j] = arr[j - 1];
}
arr[j] = tmp;
}
}
private static void swap(int[] arr, int j, int i) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void main(String[] args) {
int[] arr = {4,2,7,3,1,8,9,5};
insertSort(arr);
for(int i:arr)
{
System.out.print(i + " ");
}
}
}
快速排序
排序思想
以一个值为基准(通常以第一个值),然后将比他小的数移到它左边,比他大的数移到右边,那么问题就转化为要找到中间分隔的基准值
下面是一个栗子:
要排序数组(升序):{6,1,2,7,9,3,4,5,10,8}
————————————
使用两个指针start和end分别指向开头位置和结尾位置,将开始第一个位置的元素设为
寻找中间分隔点:
(1)只要end指针指向的值比keyValue大,就将end指针一次向前移动,最后end指针所指向的值为从后往前第一个小于keyValue的值,此时将end位置上的值赋给start位置,见下图
(2)然后从start位置开始,只要start位置上的值小于keyValue的值,就将start位置向前移,最终start停留的位置为从前往后第一个大于keyValue的位置,此时将此时的start位置的值赋给end位置,见下图
(3)然后再次将end按照(1)的规则将end指针向前移,并将end位置的值赋给start位置,
将start按照(2)的规则将start指针向后移,并将start位置的值赋给end
见下图
(4)将end按照(1)的规则向前移并赋值,将start按照(2)的规则向后移,发现两个指针重合,见下图
(5)此时将keyValue的值赋给start位置,见下图
此时,在start位置的左边的数全部比该值小,start位置右边的数全部比该值大,然后以该位置为分界点key将数组变为两个部分,再分别进行这样的操作,最终完成排序
详细见博客:http://developer.51cto.com/art/201403/430986.htm
时间复杂度
-
快速排序的时间复杂度平均为O(NlogN)
在最好情况下,每次划分所取的基准都是当前无序区的"中值"记录,划分的结果是基准的左、右两个无序子区间的长度大致相等。总的关键字比较次数:O(NlogN) -
快速排序的时间复杂度最坏为O(N^2)
最坏情况是每次划分选取的基准都是当前无序区中关键字最小(或最大)的记录,划分的结果是基准左边的子区间为空(或右边的子区间为空),而划分所得的另一个非空的子区间中记录数目,仅仅比划分前的无序区中记录个数减少一个。因此,快速排序必须做nN1次划分,第i次划分开始时区间长度为N-i+1,所需的比较次数为N-i(1≤i≤N-1),故总的比较次数达到最大值:Cmax= N*(N-1)/2=O(N^2)
空间复杂度
- 快速排序的空间复杂度为O(logN)
快速排序在系统内部需要一个栈来实现递归。若每次划分较为均匀,则其递归树的高度为O(logN),故递归后需栈空间为O(logN)
代码实现
递归实现
package quickSort;
/**
* 快速排序(递归实现)
* @author LXY
* @email 403824215@qq.com
* @date 2018/8/18 17:28
*/
public class QuickSort {
public static void quickSort(int[] arr,int start,int end)
{
if(arr == null || arr.length == 0 || start > end)
{
return;
}
int key = quickSortHelp(arr,start,end);
quickSort(arr,start,key - 1);
quickSort(arr,key + 1,end);
}
//找中间节点位置的辅助函数
private static int quickSortHelp(int[] arr, int start, int end) {
if(arr == null || arr.length == 0 || start > end)
{
return -1;
}
int keyValue = arr[start];
while (start < end)
{
while (start < end && arr[end] > keyValue)
{
//找到第一个小于keyValue的元素
end--;
}
//交换两个元素
arr[start] = arr[end];
while (start < end && arr[start] <= keyValue)
{
//找到第一个大于keyValue的元素
start++;
}
arr[end] = arr[start];
}
arr[start] = keyValue;
return start;
}
public static void main(String[] args) {
int[] arr = {4,2,7,3,1,8,9,5};
quickSort(arr,0,arr.length - 1);
for(int i:arr)
{
System.out.print(i + " ");
}
}
}
非递归实现
package quickSort;
import java.util.Stack;
/**
* 快速排序(非递归实现)
* @author LXY
* @email 403824215@qq.com
* @date 2018/8/18 17:28
*/
public class QuickSortRec {
public static void quickSort(int[] arr,int start,int end)
{
if(arr == null || arr.length == 0 || start > end)
{
return;
}
Stack<Integer> stack = new Stack<>();
if(start < end)
{
stack.push(end);
stack.push(start);
while (!stack.isEmpty())
{
int left = stack.pop();
int right = stack.pop();
int key = quickSortHelp(arr,left,right);
if(left < key)
{
//出栈元素在标记位置左边
//将标记位置和刚才出栈的元素依次入栈
stack.push(key - 1);
stack.push(left);
}
if(right > key)
{
//出栈元素在标记位置右边
//将刚才出栈的元素和标记位置的元素依次入栈
stack.push(right);
stack.push(key + 1);
}
}
}
}
//找中间节点位置的辅助函数
private static int quickSortHelp(int[] arr, int start, int end) {
if(arr == null || arr.length == 0 || start > end)
{
return -1;
}
int keyValue = arr[start];
while (start < end)
{
while (start < end && arr[end] > keyValue)
{
//找到第一个小于keyValue的元素
end--;
}
//交换两个元素
arr[start] = arr[end];
while (start < end && arr[start] <= keyValue)
{
//找到第一个大于keyValue的元素
start++;
}
arr[end] = arr[start];
}
arr[start] = keyValue;
return start;
}
public static void main(String[] args) {
int[] arr = {4,2,7,3,1,8,9,5};
quickSort(arr,0,arr.length - 1);
for(int i:arr)
{
System.out.print(i + " ");
}
}
}
堆排序
参考自:http://blog.51cto.com/shangdc/2073589
代码实现
public class HeapSortFinal {
public static void main(String[] args) {
int[] array = { 19, 38, 7, 36, 5, 5, 3, 2, 1, 0, 56 };
System.out.println("排序前:");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + ",");
}
System.out.println();
System.out.println("分割线---------------");
heapSort(array);
System.out.println("排序后:");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + ",");
}
}
//降序
public static void heapSort(int[] array) {
if (array == null || array.length == 1)
return;
buildMaxHeap(array); // 第一次排序,构建最大堆,只保证了堆顶元素是数组里最大的
for (int i = array.length - 1; i >= 1; i--) {
// 这个是什么意思呢?,经过上面的一些列操作,目前array[0]是当前数组里最大的元素,需要和末尾的元素交换
// 然后,拿出最大的元素
swap(array, 0, i);
// 交换完后,下次遍历的时候,就应该跳过最后一个元素,也就是最大的那个值,然后开始重新构建最大堆
// 堆的大小就减去1,然后从0的位置开始最大堆
maxHeap(array, i, 0);
//minHeap(array, i, 0);
}
}
// 构建堆
public static void buildMaxHeap(int[] array) {
if (array == null || array.length == 1)
return;
// 堆的公式就是 int root = 2*i, int left = 2*i+1, int right = 2*i+2;
int cursor = array.length / 2;
for (int i = cursor; i >= 0; i--) { // 这样for循环下,就可以第一次排序完成
maxHeap(array, array.length, i);
//minHeap(array, array.length, i);
}
}
// 最大堆
public static void maxHeap(int[] array, int heapSieze, int index) {
int left = index * 2 + 1; // 左子节点
int right = index * 2 + 2; // 右子节点
int maxValue = index; // 暂时定在Index的位置就是最大值
// 如果左子节点的值,比当前最大的值大,就把最大值的位置换成左子节点的位置
if (left < heapSieze && array[left] > array[maxValue]) {
maxValue = left;
}
// 如果右子节点的值,比当前最大的值大,就把最大值的位置换成右子节点的位置
if (right < heapSieze && array[right] > array[maxValue]) {
maxValue = right;
}
// 如果不相等,说明啊,这个子节点的值有比自己大的,位置发生了交换了位置
if (maxValue != index) {
swap(array, index, maxValue); // 就要交换位置元素
// 交换完位置后还需要判断子节点是否打破了最大堆的性质。最大堆性质:两个子节点都比父节点小。
maxHeap(array, heapSieze, maxValue);
}
}
// 最小堆
public static void minHeap(int[] array, int heapSieze, int index) {
int left = index * 2 + 1; // 左子节点
int right = index * 2 + 2; // 右子节点
int maxValue = index; // 暂时定在Index的位置就是最小值
// 如果左子节点的值,比当前最小的值小,就把最小值的位置换成左子节点的位置
if (left < heapSieze && array[left] < array[maxValue]) {
maxValue = left;
}
// 如果右子节点的值,比当前最小的值小,就把最小值的位置换成左子节点的位置
if (right < heapSieze && array[right] < array[maxValue]) {
maxValue = right;
}
// 如果不相等,说明啊,这个子节点的值有比自己小的,位置发生了交换了位置
if (maxValue != index) {
swap(array, index, maxValue); // 就要交换位置元素
// 交换完位置后还需要判断子节点是否打破了最小堆的性质。最小性质:两个子节点都比父节点大。
minHeap(array, heapSieze, maxValue);
}
}
// 数组元素交换
public static void swap(int[] array, int index1, int index2) {
int temp = array[index1];
array[index1] = array[index2];
array[index2] = temp;
}
}
归并排序
参考自:https://blog.csdn.net/qq_21150865/article/details/61918216
代码实现
public class MyMergrSort {
private static void sort(int[] array, int start, int end) {
if (start >= end)
return;
int mid = (start + end) >> 1;
// 递归实现归并排序
sort(array, start, mid);
sort(array, mid + 1, end);
mergerSort(array, start, mid, end);
}
// 将两个有序序列归并为一个有序序列(二路归并)
private static void mergerSort(int[] array, int start, int mid, int end) {
// TODO Auto-generated method stub
int[] arr = new int[end + 1]; // 定义一个临时数组,用来存储排序后的结果
int low = start; // 临时数组的索引
int left = start;
int center = mid + 1;
// 取出最小值放入临时数组中
while (start <= mid && center <= end) {
arr[low++] = array[start] > array[center] ? array[center++] : array[start++];
}
// 若还有段序列不为空,则将其加入临时数组末尾
while (start <= mid) {
arr[low++] = array[start++];
}
while (center <= end) {
arr[low++] = array[center++];
}
// 将临时数组中的值copy到原数组中
for (int i = left; i <= end; i++) {
array[i] = arr[i];
}
}
public static void main(String[] args) {
int[] array = { 58, 48, 69, 87, 49, 59, 25, 35, 68, 48 };
sort(array, 0, array.length - 1);
System.out.println(Arrays.toString(array));
}
}