排序算法总结

排序总结

    这里介绍了插入排序(直接、二分法),希尔排序, 直接选择排序,堆排序,冒泡排序,快速排序, 归并排序  排序算法。

 

 

 

 

 

问题描述:

假设元素为整数,按照从小到大的顺序排序。

 

 

一  直接插入排序

1. 描述

                

 

 

                2016-07-14_Insertion_sort_animation.gif

            

            上图就是对插入排序的直观解释,来自维基百科。

 

2. 复杂性分析

最好情况:已排好序的时候,只需要 n-1 次比较

时间复杂度为 O(n)

最坏情况:逆序排序,需要  n(n-1)/2 次比较  

时间复杂度为 O(n2)

平均情况:

时间复杂度为 O(n2)

 

3. 稳定性

 直接插入排序是稳定的,不会改变相同元素的相对位置。

 

代码实现(C++):

void sort_insert(vector<int> array, int size)

{

    if(size <= 1) return;


    int i, j;

    for(i = 1; i < size; ++i)

        if(array[i] < array[i - 1])

        {

            int temp = array[i];

            for(j = i - 1; array[j] > temp; --j) array[j + 1] = array[j];

            array[j + 1] = temp;

        }

}

 

 

 

 

二 二分插入排序

 1. 描述 

在直接插入排序中,为了找到合适的插入位置,我们采用的是从后到前的顺序查找进行比较,为了减少比较的次数,采用二分查找。

 

2. 复杂度分析

时间复杂度:

最好情况:O(log2n)

最坏情况:O(n2)

平均情况:O(n2)

 

3. 稳定性

稳定排序。

 

 

代码实现(C++)

 
void BinarySearch(int array[], int start, int end, int k)

{

    while(start <= end)

    {

        int middle = (start + end) / 2;

        if(array[middle] > k)

            end = middle - 1;

        else

            start = middle + 1;

    }

    return start;

}

void sort_insert(int array[], int size)

{

    if(size <= 1) return ;

    int i, j;

    for(i = 1; i < size; ++i)

    {

        if(array[i] < array[i - 1])

        {

            int temp = array[i];

            int insert_index = BinarySearch(array, 0, i, array[i]);

            for(j = i - 1; j >= insert_index, --j) array[j + 1] = array[j];

            array[insert_index] = temp;

        }

    }
}

 

 

三  希尔排序

1. 描述

     希尔排序是插入排序的一种,是直接插入排序的一种改进版本,是DL. Shell 于1959年提出的。

     将待排序序列划分为若干组,在每一组内进行插入排序,以使整个序列基本有序,直至序列的间隔为1。

 

         

 

2. 复杂性分析

        希尔排序的时间复杂度与增量序列的选取有关。

        希尔增量的时间复杂度为O(n2) {N/2, (N/2)/2, ..., 1}

        Hibbard增量的 时间复杂度为 O(n3/2) {2k-1, ..., 3, 1}

        

 

3. 稳定性

    不稳定排序。

 

代码实现(C++)

void sort_shell(int array[], int size)

{

    int gap = size;

    while(gap > 1)

    {

        gap = gap / 3 + 1;

        for(int i = gap; i < size; ++i)

        {

            if(array[i] < array[i - gap])

            {

                int temp = array[i];

                int j = i - gap;

                while(j >= 0 && array[j] > temp)

                {

                    array[j + gap] = array[j];

                    j = j - gap;

                }

                array[j + gap] = temp;

            }

        }

    }

}

 

 

 

 

四 直接选择排序

1. 描述

直接选择排序指每次选择所要排序的数组中的最小值的数组元素,将这个数组元素与最前面没有进行排序的数组元素交换。

 

 

2. 复杂度分析

在一个长度为N的数组中,第一趟遍历N个数,找出最小的数与第一个元素互换,

...

第N-1趟遍历 2 个数,找出最小的数与第N-1 个元素互换。

 

时间复杂度

比较:O(n2)

交换:O(n)  数据移动是最少的。

 

 

3. 稳定性

不稳定排序

 

代码实现(C++)
 

void sort_selection(vector<int> &array, int size)

{

    for(int i = 0; i < size - 1; ++i)

    {

        int index = i;

        for(int j = i + 1; j < size; ++j)

            if(array[j] < array[index]) index = j;

        if(index != i)

        {

            int temp = array[index];

            array[index] = array[i];

            array[i] = temp;

        }

    }

}

 

五  堆排序

参考页面:http://www.cnblogs.com/0zcl/p/6737944.html 

堆排序也属于选择排序的一种。

给定一个列表array=[16, 7, 3, 20, 17, 8],对其进行堆排序。

2016-07-15_å æåº.gif

1. 过程描述

 

首先根据该数组元素构建一个完全二叉树,得到

然后需要构造初始堆,则从最后一个非叶节点开始调整,调整过程如下:

第一步: 初始化大顶堆(从最后一个有子节点开始往上调整最大堆)

20和16交换后导致16不满足堆的性质,因此需重新调整

 

得到了初始堆。

 

第二步: 堆顶元素R[1]与最后一个元素R[n]交换,交换后堆长度减一

即每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整)。有了初始堆之后就可以进行排序了。

 

第三步: 重新调整堆。此时3位于堆顶不满堆的性质,则需调整继续调整(从顶点开始往下调整)

 

重复上面的步骤。

 

 

 

2. 复杂性分析

最好,最坏,平均 :O(nlog2(n))

 

 

3. 稳定性

不稳定排序

 

使用情况:

选择最小(大)的k个数, 可以使用堆排序,复杂情况变为O(nlog2(k))

 

代码实现(C++)

void adjust_heap(vector<int> &data, int parent, int end)
{
    int child = 2 * parent + 1;
    while(child < end)
    {
        // if the parent node have right child and the value of the right child is bigger than the value of the left child.
        if(child + 1 < end && data[child + 1] > data[child]) ++child;
        
        // the value of parent node is bigger than its child nodes
        if(data[parent] >= data[child]) break;
        
        swap(data[parent], data[child]);
        parent = child;
        child = 2 * child + 1;
    }
}
    
void sort_heap(vector<int> &data)
{
    // the initial heap
    for(int i = data.size() / 2; i >= 0; --i)
        adjust_heap(data, i, data.size() - 1);
    
    for(int i = data.size() - 1; i >= 0; --i)
    {
        swap(data[i], data[0]);
        adjust_heap(data, 0, i);
    }
}

 

 

六  冒泡排序

1. 描述

 2016-07-14_å泡æåº.gif

 

2. 复杂性分析

 

最好情况:

    数据正序,只需要走一趟就可完成。 O(n)

最坏情况:

    数据是反序的,需要进行n-1趟排序。没趟排序需要进行 n-i 次比较, 比较次数为 (n(n-1)/2) = O(n2)。

平均情况:

    O(n2)

 

3. 稳定性

    稳定排序

 

数组中逆序对的个数,即冒泡排序的交换次数。

 

代码实现(C++)

void sort_bubble(vector<int> &array, int size)

{

    for(int i = 0; i < size - 1; ++i)

        for(int j = 0; j < size - 1 - i; ++j)

            if(array[j] > array[j + 1]) swap(array[j], array[j + 1]);

}

 

 

七 快速排序

1. 描述

    快速排序是对冒泡排序的一种改进,基本思想是选取一个记录作为枢轴,经过一趟排序,将整段序列分为两个部分,一部分的值小于枢轴,另一部分的值大于枢轴。循环进行此过程,直至有序。

    2016-07-14_Sorting_quicksort_anim.gif

    挖坑法

    初始数据:   72, 6, 57, 88, 60, 42, 83, 73, 48, 85

                            [i = 0, j = 9]

    第一趟排序的过程为:

           ① 先挖坑,将i = 0 挖出来,记为 temp = 72

           ② 从右边开始寻找比 temp小的数,将其赋值给 i 指向的元素。

                    48, 6, 57, 88, 60, 42, 83, 73, 48, 85

                    [i = 0, j = 8]

           ③ 从左边开始寻找比 temp大 的数, 将其赋值给 j 指向的元素。

                   48, 6, 57, 88, 60, 42, 83, 73, 88, 85     

                    [i = 3, j = 8]

           ④ 重复②③, 直到 i == j 

           ⑤  将 temp填入 i 指向的坑中。

                (48, 6, 57, 42, 60) 72 (83, 73, 88, 85)

                [i = j = 5 ]       

    左右指针法

        左右交换,最后一步要注意,交换 array[i], array[left]。

 

2. 复杂性分析

    最坏情况 O(n2)

    最好情况 O(nlogn)

 

3. 稳定性

    不稳定排序。

 

代码实现(C++)

// 挖坑法
void sort_quick(vector<int> &array, int left, int right)
{
	if (left >= right)	return;
	
	int i	 = left;
	int j	 = right;
	
	int temp = array[left];
	while (i < j)
	{
		while (j > i && array[j] >= temp)	--j;
		if (j > i)	array[i] = array[j];
		
		while (i < j && array[i] <= temp)	++i;
		if (i < j)	array[j] = array[i];
	}
	array[i] = temp;
	
	sort_quick(array, left, i - 1);
	sort_quick(array, i + 1, right);
}

 

void sort_quick2(int array[], int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int i = left;
	int j = right;
	int temp = array[left];
	while (i < j)
	{
		while (j > i && array[j] >= temp)	--j;
		
		while (i < j && array[i] <= temp)	++i;
		
		swap(array[i], array[j]);
	}
	array[left] = array[i];
	array[i] = temp;
	
	sort_quick(array, left, i - 1);
	sort_quick(array, i + 1, right);
}

 

 

 

 

 八 归并排序

参考页面:http://www.cnblogs.com/chengxiao/

 1. 描述

    分而治之。 

    归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

    

动画演示:

2016-07-15_å½å¹¶æåº.gif

 

2. 复杂性分析

最好、最坏、平均情况都是 O(nlogn)

 

3. 稳定性

    稳定排序

 

代码实现(C++)

#inlcude <iostrem>
#include <vector>

using namespace std;

void merge(vector<int> &array, vector<int> &temp, int begin, int end)
{
	if(begin == end) return;

	// divide
	int middle = begin + ((end - begin) >> 1);
	merge(array, temp, begin, middle);
	merge(array, temp, middle + 1, end);

	// conquer
	int i = begin;
	int j = middle + 1;
	int index = begin;
	while(i <= middle && j <=  end)
	{
		if(array[i] < array[j]) 
			temp[index++] = array[i++];
		else 
			temp[index++] = array[j++];
	}

	while(i <= middle) temp[index++] = array[i++];
	while(j <= end)    temp[index++] = array[j++];

	for(int i = begin; i <= end; ++i) array[i] = temp[i]; 
}

void sort_merge(vector<int> &array)
{
    // 额外空间: O(n)
	vector<int> temp(array);
	merge(array, temp, 0, array.size() - 1);
}

 

 

 

注:有些图片未找见出处,如侵权,请告知。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值