==============================
冒泡排序算法
==============================
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><PREclass=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,constint low,constint 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, constint 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,constint 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,constint size,constint 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,constint 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,constint 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="COLOR: #ff0000; FONT-SIZE: 13px"></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,constint 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);
- }
- }