==============================
冒泡排序算法
==============================
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>