1.堆排序
// 根据数组构建大堆
void HeapAdjust( int* arr, int i, int len ){
int child;
int tmp;
for (; 2 * i + 1 < len; i = child){
// 子结点 = 2 * 父结点 + 1
child = 2 * i + 1;
// 得到子结点中数值较大的结点
if (child < len-1 && arr[child+1] > arr[child])
++child;
// 如果较大的子结点值大于父结点的值那么把较大的子结点往上移动,替换它的父结点
if (arr[i] < arr[child]){
tmp = arr[i];
arr[i] = arr[child];
arr[child] = tmp;
}
else
break;
}
}
//堆排序算法
void HeapSort( int* arr, int len ){
int i;
// length/2-1是最后一个非叶节点
// 构建大堆
for (i = len/2-1; i >= 0; --i)
HeapAdjust( arr, i, len );
// 从最后一个元素开始对序列进行调整,不断缩小调整范围直到第一个元素
for (i = len-1; i > 0; --i){
// 把第一个元素和当前的最后一个元素交换
// 保证当前最后一个位置的元素都是现在这个序列之中最大的元素
arr[i] = arr[0] ^ arr[i];
arr[0] = arr[0] ^ arr[i];
arr[i] = arr[0] ^ arr[i];
// 不断缩小调整范围,每一次调整完毕后保证第一个元素是当前序列的最大值
HeapAdjust( arr, 0, i );
}
}
2.
希尔排序(插入排序的改进版本),希尔排序的步长现在还没有最好的选择,不过已知的最好步长序列是由Sedgewick提出的(1, 5, 19, 41, 109,...)
操作步骤:
初始时,有一个大小为 10 的无序序列。
(1)在第一趟排序中,我们不妨设 gap1 = N / 2 = 5,即相隔距离为 5 的元素组成一组,可以分为 5 组。
(2)接下来,按照直接插入排序的方法对每个组进行排序。
在第二趟排序中,我们把上次的 gap 缩小一半,即 gap2 = gap1 / 2 = 2 (取整数)。这样每相隔距离为 2 的元素组成一组,可以分为 2 组。
(3)按照直接插入排序的方法对每个组进行排序。
(4)在第三趟排序中,再次把 gap 缩小一半,即gap3 = gap2 / 2 = 1。 这样相隔距离为 1 的元素组成一组,即只有一组。
(5)按照直接插入排序的方法对每个组进行排序。此时,排序已经结束。
时间复杂度:
最好情况:由于希尔排序的好坏和步长gap的选择有很多关系,因此,目前还没有得出最好的步长如何选择(现在有些比较好的选择了,但不确定是否是最好的)。所以,不知道最好的情况下的算法时间复杂度。
最坏情况下:O(N*N)
空间复杂度:
由直接插入排序算法可知,我们在排序过程中,需要一个临时变量存储要插入的值,所以空间复杂度为O(1)。
算法稳定性
希尔排序中相等数据可能会交换位置,所以希尔排序是不稳定的算法。
在使用 增量k的一趟排序后,对于每一个i,我们有 a[i] <= a[i + k]。所有相隔k的元素都被排序。希尔排序一个重要性质:一个k排序的文件保持它的k排序性。如果情况不是这样,该排序就没有意义了,因为后面各趟排序会打乱前面各趟排序的成果。
k排序的一般做法:对于i,i+k,i+2k...中的每一个位置,都把其上的元素放到前面序列中的正确位置上。
一趟k排序作用是对k个独立的子数组进行一次插入排序。
template <typename T>
void ShellSort( vector<T>& a ){
for (int gap = a.size( ) / 2; gap > 0; gap /= 2)
for (int i = gap; i < a.size( ); ++i){
T tmp = a[i];
int j = i;
for (; j >= gap && tmp < a[j - gap]; j-= gap)
a[j] = a[j - gap];
a[j] = tmp;
}
}
3.
归并排序
合并两个已排序的表时间是线性的,最多进行N-1次比较,N是两个表元素的总数,因为每次比较都把一个元素添加到临时数组中,但最后一次比较除外,它至少添加两个元素。
如果对Merge的每个递归调用均局部声明一个临时数组,那么在任一时刻就可能有 logN个临时数组处在活动期。 由于Merge是MergeSort的最后一行,因此在任一时刻只需要一个临时数组活动,而且这个临时数组可以在MergeSort驱动程序中建立。还可以使用临时数组的任意部分。我们将使用与输入数组a相同的部分
template <typename T>
void MergeSort( vector<T>& a ){
vector<T> tmpArr( a.size( ) );
MergeSort( a, tmpArr, 0, a.size( ) - 1 );
}
template <typename T>
void MergeSort( vector<T>& a, vector<T>& tmpArr, int left, int right ){
if (left < right){
int center = ( left + right ) / 2;
MergeSort( a, tmpArr, left, center );
MergeSort( a, tmpArr, left+1, right );
Merge( a, tmpArr, left, center+1, right );
}
}
template <typename T>
void Merge( vector<T>& a, vector<T>& tmpArr, int leftPos, int rightPos, int rightEnd ){
int leftEnd = rightPos - 1;
int tmpPos = leftPos;
int num = rightPos - leftPos + 1;
while (leftPos <= leftEnd && rightPos <= rightEnd)
if (a[leftPos] <= a[rightPos])
tmpArr[tmpPos++] = a[leftPos++];
else
tmpArr[tmpPos++] = a[rightPos++];
while (leftPos <= leftEnd)
tmpArr[tmpPos++] = a[leftPos++];
while (rightPos <= rightEnd)
tmpArr[tmpPos++] = a[rightEnd++];
for (int i = 0; i < num; i++, rightEnd--)
a[rightEnd] = tmpArr[rightEnd];
}
4.
实现时间复杂度为O(nlogn)的链表排序算法
链接:点击打开链接
链表排序是一道常见的关于链表的算法题,往往我们会使用冒泡排序或者选择排序这两种算法来解决这个问题。但是它们的时间复杂度是O(n²),效率不高。所以今天我们要实现时间复杂度O(nlogn)的算法就不能再选择这两种排序算法。而满足这个时间复杂度的排序算法只有快速排序,堆排序和归并排序。但是因为快排和堆排是不稳定的,所以我选择了归并排序算法来实现链表排序问题。
下面给出代码实现
class ListNode
{//结构定义
public:
int val;
ListNode *next;
ListNode(int val)
{
this->val = val;
this->next = NULL;
}
};
ListNode* SortList(ListNode* head)
{
if (head == NULL || head->next == NULL)
return head;
ListNode* fast;
ListNode* slow;
while (fast->next != NULL && fast->next->next != NULL)
{//通过快慢指针法寻找中间结点
fast = fast->next;
fast = fast->next;
slow = slow->next;
}
ListNode* mid = slow->next;
slow->next == NULL;//把链表拆分开为两个链表
//分治思想,递归
ListNode* list1 = SortList(head);
ListNode* list2 = SortList(mid);
ListNode* sorted = Merge(list1, list2);
return sorted;
}
ListNode* Merge(ListNode* list1, ListNode* list2)
{//合并
if (list1 == NULL)
return list2;
if (list2 == NULL)
return list1;
ListNode* head;
ListNode* tmp;
if (list1->val < list2->val)
{
head = list1;
list1 = list1->next;
}
else
{
head = list2;
list2 = list2->next;
}
tmp = head;
while (list1 && list2)
{
if (list1->val < list2->val)
{
tmp->next = list1;
tmp = tmp->next;
list1 = list1->next;
}
else
{
tmp->next = list2;
tmp = tmp->next;
list2 = list2->next;
}
}
if (list1 == NULL)
{
tmp->next = list2;
}
if (list2 == NULL)
{
tmp->next = list1;
}
return head;
}