您的位置:首页 > 其它

排序-算法总结

2016-05-22 21:43 369 查看
==============================

冒泡排序算法

==============================

1.算法稳定性

稳定

2.复杂度

空间复杂度O(1); 平均时间复杂度O(n2)

3.极限情况分析

最好情况:向量本来就是有序的,则一趟扫描即可结束,共比较n-1次,无交换。

最坏情况:向量是逆序的,则一共需要做n-1次扫描,每次扫描都必须比较n-i次,由等差数列求和公式知道,一共需做n(n-1)/2次比较和交换。

4.算法实现

<span style="font-size:13px;"><span style="font-size:13px;"></span></span><pre class="cpp" name="code">template<typename T>

void bubble_sort(T * array, const int size)

{

if(size < 2 || array == NULL)

{

cout << "illegal input!" << endl;

return;

}

bool exchanged = false;

T tmp;

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

{

exchanged = false;

for(int j=size-1; j>i; --j)

{

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

{

tmp = array[j];

array[j] = array[j-1];

array[j-1] = tmp;

exchanged = true;

}

}

if(!exchanged)

{

return;

}

}

}

</pre>

<pre></pre>

<p><span style="font-size:13px">5. 算法改进<br>

(1) 奇偶交换排序<br>

稳定性? 稳定 <span style="color:#ff0000">//TODO need verifying<br>

</span> 空间复杂度O(1), 时间复杂度O(n2)<br>

极限情况分析:正向有序:n-1次比较;逆向有序((n+1)/2)*n+(n/2-1)次比较<br>

综合而言,性能和冒泡排序相当,所以一般不用此种排序方法。<br>

算法实现:</span></p>

<pre class="cpp" name="code"><span style="font-size:13px;">template<typename T>

void oe_sort(T * array, const int size)

{

if(size < 2 || array == NULL)

{

cout << "illegal input!" << endl;

return;

}

T tmp;

bool exchanged = true;

while(exchanged)

{

exchanged = false;

//scan odd index

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

{

if(array[i] > array[i+1])

{

tmp = array[i];

array[i] = array[i+1];

array[i+1] = tmp;

exchanged = true;

}

}

//scan even index

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

{

if(array[j] > array[j+1])

{

tmp = array[j];

array[j] = array[j+1];

array[j+1] = tmp;

exchanged = true;

}

}

}

}

</span></pre>

<p><br>

<br>

<span style="font-size:13px"> <br>

(2) 每次扫描记住最后一次扫描发生的位置lastExchange<br>

如此一样,下一趟排序时区间[0, lastExchange-1]是有序区, [lastExchange, size-1]是无序区。如果不记录,则下一趟排序时区间[0, i]有序区间,i为上次排序的趟数。自然的,lastExchange的最小可能数值就是i,所以此种改性是有效的。<br>

算法性能和普通冒泡一样。<br>

算法实现: </span><span style="font-size:13px"><span style="color:#ff0000">//TODO<br>

</span> <br>

(3) 改进冒泡算法的不对称性<br>

什么是不对称性? 比如上面的冒泡排序算法,如果数组中的最大值位于a[0],理论上一遍扫描就可以完成排序,但实际仍旧需要n-1遍扫描。而如果改冒泡为沉底,则一遍即可。<br>

所以引出了解决不对称性的方法:冒泡和沉底交替进行。<br>

算法性能和普通冒泡一样。<br>

算法实现: </span><span style="font-size:13px"><span style="color:#ff0000">//TODO<br>

</span> <br>

<br>

==========================<br>

快速排序算法<br>

==========================<br>

1. 基本思想<br>

分治法:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。</span></p>

<p><span style="font-size:13px">2. 算法描述<br>

核心是:选取哨兵结点,划分区域,并找到哨兵结点所在的位置。<br>

(1) 选取一个哨兵结点,通常取首结点,或者末结点,或者中间结点。但最合理的应该是随机选择一个结点。<br>

(2) 根据哨兵结点,将原数组划分为左右2个部分,并决定哨兵结点的位置。<br>

(3) 分别对左右部分递归地进行(1)和(2),递归结束条件是分出的子数组长度小于等于1</span></p>

<p><span style="font-size:13px">3. 算法稳定性<br>

不稳定</span></p>

<p><span style="font-size:13px">4. 复杂度<br>

平均空间复杂度是O(lgn), 注意是以2为底的对数。<br>

平均时间复杂度是O(nlgn)。</span></p>

<p><span style="font-size:13px">5. 极限情况分析<br>

最好情况下,空间复杂度是O(lgn), 时间复杂度是O(nlgn)。此处的空间复杂度主要是递归产生的栈空间。<br>

最坏情况下,空间复杂度是O(n), 时间复杂度是O{n2}。最坏情况是每次哨兵结点都是整个数组的最大值,或者最小值。<br>

<br>

6. 算法实现<br>

</span></p>

<pre class="cpp" name="code">template<typename T>

int partition(T * array, const int low, const int high)

{

//select the first elem as pivot. Here use random index for pivot will be better

T pivot = array[low];

for(int i=low, j=high; i<j;)

{

//scan for the first elem that smaller than pivot

while(j>i && array[j]>=pivot)

j--;

if(i<j)

{

//since array[i] is already be array[j] which is smaller than pivot, array[i+1] will firstly checkde in next scan

array[i++] = array[j];

}

//scan for the first elem that bigger than pivot

while(i<j && array[i]<=pivot)

i++;

if(i<j)

{

array[j--] = array[i];

}

}

//put the pivot to right position, pls notice that i will be equal with j at this point

array[i] = pivot;

return i;

}

template<typename T>

quick_sort(T * array, const int low, const int high)

{

int pivotPos = 0;

if(low < high)

{

//the most important is locating index for pivot, then partition will be possible

pivotPos = partition(array, low, high);

quick_sort(array, low, pivotPos-1);

quick_sort(array, pivotPos+1, high);

}

}

</pre>

<p><span style="font-size:13px">注意:冒泡排序和快速排序均属于交换排序算法。</span></p>

<p><span style="font-size:13px"> </span></p>

<p><span style="font-size:13px">========================<br>

直接插入排序算法<br>

========================<br>

1.算法稳定性<br>

稳定<br>

<br>

2.复杂度<br>

空间复杂度O(1); 时间复杂度O(n2)<br>

<br>

3.极限情况分析<br>

最好情况:向量是正序的,共比较关键字n-1次,无交换。<br>

最坏情况:向量是逆序的,则一共比较关键字│(n+2)(n-1)/2次,记录移动次数n-1)(n+4)/2次<br>

<br>

4. 算法实现<br>

</span></p>

<pre class="cpp" name="code">template<typename T>

void insert_sort(T * array, const int size)

{

if(size < 2 || array == NULL)

{

cout << "illegal input!" << endl;

return;

}

T pivot;

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

{

if(array[i] >= array[i-1])

{

continue;

}

pivot = array[i];

int j = i-1;

do

{

array[j+1] = array[j];

--j;

}while(pivot < array[j] && j>=0);

array[j+1] = pivot;

}

}

</pre>

<p> </p>

<p><span style="font-size:13px">========================<br>

希尔排序算法<br>

========================<br>

1.算法稳定性<br>

不稳定<br>

<br>

2.算法描述<br>

(1)直接插入排序的变体,取一个小于数组长度n的整数d作为初始增量,将整个数组分为d个小组。<br>

比如说整个数组的长度为7,选择初始增量d为5, 则a[0], a[5]为第一组。a[1]和a[6]为第二组,a[2]和a[7]为第三组,a[3]为第四组,a[4]为第五组<br>

然后在每个小组内进行直接插入排序。<br>

(2)按照一定的减少d, 比如说第二次改变为3,再次进行上述的(1)和(2)。直到d减少到为1,并最后一次执行直接排序为止。<br>

<br>

3.复杂度<br>

空间复杂度O(1); 时间复杂度O(n(lgn)2) 译为n乘以lgn的平方。其复杂度收到增量递减方式的影响。最好的步长序列由Marcin Ciura设计为{1, 4, 10, 23, 57, 132, 301, 701, 1750, ...}<br>

<br>

4.极限情况分析<br>

最好情况:向量是正序的,共比较关键字n-1次,无交换。<br>

最坏情况:向量是逆序的,则一共比较关键字│(n+2)(n-1)/2次,记录移动次数n-1)(n+4)/2次<br>

<br>

5.算法实现</span></p>

<pre class="cpp" name="code">template<typename T>

void shellInteration(T * array, const int size, const int increment)

{

T pivot;

for(int i=increment; i<size; ++i) //此算法中,将所有的increment改成1,就退化成了直接插入排序算法

{

if(array[i] >= array[i-increment])

{

continue;

}

pivot = array[i];

int j = i-increment;

do

{

array[j+increment] = array[j];

j -= increment;

}while(pivot < array[j] && j>=0);

array[j+increment] = pivot;

}

}

template<typename T>

void shellSort(T * array, const int size)

{

if(size < 2 || array == NULL)

{

cout << "illegal input!" << endl;

return;

}

int increaArray[3] = {5, 3, 1}; //增量序列,必须以1结束

for(int i=0; i<sizeof(increaArray)/sizeof(int); ++i)

{

shellInteration(array, size, increaArray[i]);

}

}

</pre>

<p> </p>

<p><span style="font-size:13px">注意:直接插入排序和希尔排序都是插入排序。</span></p>

<p><span style="font-size:13px"> </span></p>

<p><span style="font-size:13px">========================<br>

直接选择排序算法<br>

========================<br>

1. 基本思想<br>

进行n-1次扫描,每次从无序区中选择关键字最小的记录,并移动到有序区中。</span></p>

<p><span style="font-size:13px">2. 算法稳定性<br>

不稳定</span></p>

<p><span style="font-size:13px">3. 复杂度<br>

平均空间复杂度是O(l)。<br>

平均时间复杂度是O(n2)。</span></p>

<p><span style="font-size:13px">4. 极限情况分析<br>

最好情况下,空间复杂度是O(1), 时间复杂度是O(n2)。此处的空间复杂度,即便在正向有序的情况下,第i趟都需要比较关键字n-i次。<br>

最坏情况下,空间复杂度是O(1), 时间复杂度是O{n2}。</span></p>

<p><span style="font-size:13px">5. 算法实现</span></p>

<pre class="cpp" name="code">template<typename T>

void select_sort(T * array, const int size)

{

if(size < 2 || array == NULL)

{

cout << "illegal input!" << endl;

return;

}

//pivotIndex index to monitor the smallest elem in every scan

int pivotIndex = 0;

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

{

pivotIndex = i;

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

{

if(array[j] < array[pivotIndex])

pivotIndex=j;

}

if(pivotIndex!=i)

{

T tmp = array[pivotIndex];

array[pivotIndex] = array[i];

array[i] = tmp;

}

}

}

</pre>

<p><br>

<span style="font-size:13px">========================<br>

堆排序算法<br>

========================<br>

1. 基本思想<br>

在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。</span></p>

<p><span style="font-size:13px">2. 算法稳定性<br>

不稳定</span></p>

<p><span style="font-size:13px">3. 复杂度<br>

平均空间复杂度是O(l)。<br>

平均时间复杂度是O(nlogn)。</span></p>

<p><span style="font-size:13px">4. 极限情况分析<br>

最坏情况下,时间复杂度是O(nlogn)。</span></p>

<p><span style="font-size:13px">5. 算法实现</span></p>

<pre class="cpp" name="code"><span style="font-size:13px;">template<typename T>

void heapAdjust(T * array, const int startIndex, const int length)

{

//注意此函数的功能不是建立大根堆,只是做有限的调整

int pivotIndex = startIndex;

while(2*pivotIndex+1 < length)

{

int lChildIndex = 2*pivotIndex+1, rChildIndex = 2*pivotIndex+2;

//取得左右孩子中较大值的下标

int maxChildIndex = lChildIndex;

if(rChildIndex < length)

{

maxChildIndex = array[lChildIndex]<array[rChildIndex]?rChildIndex:lChildIndex;

}

//如果孩子结点的值大于父亲结点的值,则交换2个结点值

if(array[maxChildIndex] > array[pivotIndex])

{

T tmp = array[pivotIndex];

array[pivotIndex] = array[maxChildIndex];

array[maxChildIndex] = tmp;

//交换后,子堆被破坏,继续调整子堆

pivotIndex = maxChildIndex;

}

else

{

break; //如果已经符合了大根堆的规则,则直接退出

}

}

}

template<typename T>

void heap_sort(T * array, const int size)

{

if(size < 2 || array == NULL)

{

cout << "illegal input!" << endl;

return;

}

//第一步,把数组看成是完全二叉树的层次遍历存储,并将整个数组建成一个大根二叉堆

//size/2-1所标记的结点,完全二叉树的最后一个叶子结点的父结点

for(int i=size/2-1; i>=0; --i)

{

heapAdjust(array, i, size);

//test

//printResult(cout, array, 10);

}

//经过第一步后,array[0]存储的是此数组中最大值,将array[0]和array[size-1]交换,这样无序区就变成了

//array[0, size-2],再对无序区进行大根堆的调整。

//这里需要注意一点:array[0, size-2]成为不符合大根堆原则的区间,仅仅因为调整后的根结点array[0]位置不对,

//所以只需要将array[0]“下沉交换”到合适的层次,使得整个区间再次成为大根堆时,array[0]变再次成为子区间中的最大值。依次类推。

for(int j=size-1; j>0; --j)

{

T tmp = array[0];

array[0] = array[j];

array[j] = tmp;

heapAdjust(array, 0, j); //TODO: I suspect that it should be j-1 here

}

}

</span></pre>

<p> </p>

<p><span style="font-size:13px">注意:直接选择排序和堆排序都属于选择排序的范畴。</span></p>

<p><br>

<span style="font-size:13px">========================<br>

箱排序算法<br>

========================<br>

1. 基本思想<br>

箱排序也称桶排序(Bucket Sort),其基本思想是:设置若干个箱子,依次扫描待排序的记录R[0],R[1],…,R[n-1],把关键字等于k的记录全都装入到第k个箱子里(分配),然后按序号依次将各非空的箱子首尾连接起来(收集)。</span></p>

<p><span style="font-size:13px">2. 算法稳定性<br>

如果保证元素入箱和出箱时都遵守FIFO,则箱排序是稳定的。<br>

<br>

3. 复杂度<br>

平均空间复杂度是O(n)。<br>

平均时间复杂度是O(m+n)。其中,m是箱子的个数。如果箱子个数的数量级是O{n},则时间复杂度是O{n}<br>

<br>

4. 极限情况分析<br>

最坏情况下,时间复杂度是o{n2}。比如说一个有10个元素的数组排序,元素的取值范围是1-100, 用100个箱子进行排序,此时m=n2=100, 时间复杂度也变成了O(n2).</span></p>

<p><span style="font-size:13px">5. 算法实现<br>

<span style="color:#ff0000">//TODO</span></span></p>

<p><span style="font-size:13px; color:#ff0000"></span> </p>

<p><span style="font-size:13px">========================<br>

基数排序算法<br>

========================<br>

1. 基本思想<br>

定义比较抽象,以十进制的整数数组为例说明。一个十进制整数的每一位的取值范围是0-9,可能的取值个数是10,这个可能的取值个数成为基数。<br>

再比如是英文小写字符数组,则取值范围是'a'到'z',一共26个可能的取值,基数则为26。<br>

首先设置基数个箱子,比如整数数组排序,则设置10个箱子,序号从0到9。然后从低位到高位对每个元素进行箱排序。排序所需要进行的趟数,是最大的元素位数。比如说一个整数数组,最大元素是1000,其它元素都小于1000,则需要进行4趟扫描。</span></p>

<p><span style="font-size:13px">2. 算法稳定性<br>

要保证基数排序的正确性,必须保证除开第一趟外的每趟箱排序是稳定的。这点保证了基数排序本身也是稳定的。<br>

<br>

3. 复杂度<br>

平均时间复杂度 O{n}<br>

平均空间复杂度 O(n+基数)</span></p>

<p><span style="font-size:13px">4. 算法实现<br>

<span style="color:#ff0000"> //TODO</span><br>

</span></p>

<p><span style="font-size:13px"></span><span style="font-size:13px"><br>

========================<br>

归并排序算法<br>

========================<br>

1. 基本思想<br>

算法导论上第一章中谈分治法时的例子,基于二路归并操作(对2个有序数组进行合并,形成新的有序序列)。首先将整个数组不断二分直至只有一个元素(已然有序),然后对子数组进行二路归并操作,最后合并操作结果。</span></p>

<p><span style="font-size:13px">2. 算法的稳定性<br>

是稳定的。</span></p>

<p><span style="font-size:13px">3. 复杂度<br>

平均时间复杂度 O{nlgn}, 空间复杂度O{n}。就时间复杂度为言,和快速排序一样,但快速排序是就地排序。<br>

<br>

4. 算法实现</span></p>

<pre class="cpp" name="code">//merge [left, middle] and [middle+1, right], then copy back to array

template<typename T>

void mergeAndCopy(T * array, const int left, int middle, int right)

{

T * buff = new T[right-left+1];

//merge two part of array

int i=left, j=middle+1;

int buff_index=0;

while(i<=middle && j<=right)

{

buff[buff_index++] = array[i]<=array[j]?array[i++]:array[j++];

}

//merge the rest part

while(i<=middle)

{

buff[buff_index++] = array[i++];

}

while(j<=right)

{

buff[buff_index++] = array[j++];

}

//copy buff data back to original array

for(int k=left, index=0; k<=right; ++k, ++index)

{

array[k] = buff[index];

}

}

template<typename T>

void mergeSort(T * array, const int left, const int right)

{

if(NULL == array)

{

cout << "illegal input" << endl;

return;

}

if(left < right)

{

int middle = (left + right)/2;

mergeSort(array, left, middle);

mergeSort(array, middle+1, right);

mergeAndCopy(array,left,middle,right);

}

}

</pre>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: