[C++]排序模板(含C++模板代码)

排序模板

一、插入排序

  • 特点:stable sort、In-place sort
  • 最优复杂度:当输入数组就是排好序的时候,复杂度为O(n),而快速排序在这种情况下会产生O(n^2)的复杂度。
  • 最差复杂度:当输入数组为倒序时,复杂度为O(n^2)
  • 插入排序比较适合用于“少量元素的数组”。

伪代码:

image

C++模板:

template <typename T>
void Insertion_Sort(T *array, size_t length) {
    if (length <= 1) {
        return;
    } else {
        for (int i = 1; i != length; i++) {
            int j = i - 1;
            T key = array[i];
            while (j >= 0 && array[j] > key) {
                array[j + 1] = array[j];
                j--;
            }
            array[j + 1] = key;
        }
    }
}

证明算法正确性:

循环不变式:在每次循环开始前,A[1…i-1]包含了原来的A[1…i-1]的元素,并且已排序。

初始:i=2,A[1…1]已排序,成立。

保持:在迭代开始前,A[1…i-1]已排序,而循环体的目的是将A[i]插入A[1…i-1]中,使得A[1…i]排序,因此在下一轮迭代开 始前,i++,因此现在A[1…i-1]排好序了,因此保持循环不变式。

终止:最后i=n+1,并且A[1…n]已排序,而A[1…n]就是整个数组,因此证毕。

二、冒泡排序

  • 特点:stable sort、In-place sort
  • 思想:通过两两交换,像水中的泡泡一样,小的先冒出来,大的后冒出来。
  • 最坏运行时间:O(n^2)
  • 最佳运行时间:O(n^2)(当然,也可以进行改进使得最佳运行时间为O(n))

伪代码:

image

C++模板:

template <typename T>
void Bubble_Sort(T *array, size_t length) {
    for (int i = 0; i != length - 1; i++) {
        for (int j = 0; j + i != length - 1; j++) {
            if (array[j] > array[j + 1]) {
                T temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
}

证明算法正确性:

运用两次循环不变式,先证明第4-6行的内循环,再证明外循环。

内循环不变式:在每次循环开始前,A[j]是A[j…n]中最小的元素。

初始:j=n,因此A[n]是A[n…n]的最小元素。
保持:当循环开始时,已知A[j]是A[j…n]的最小元素,将A[j]与A[j-1]比较,并将较小者放在j-1位置,因此能够说明A[j-1]是A[j-1…n]的最小元素,因此循环不变式保持。
终止:j=i,已知A[i]是A[i…n]中最小的元素,证毕。

接下来证明外循环不变式:在每次循环之前,A[1…i-1]包含了A中最小的i-1个元素,且已排序:A[1]<=A[2]<=…<=A[i-1]。

初始:i=1,因此A[1..0]=空,因此成立。
保持:当循环开始时,已知A[1…i-1]是A中最小的i-1个元素,且A[1]<=A[2]<=…<=A[i-1],根据内循环不变式,终止时A[i]是A[i…n]中最小的元素,因此A[1…i]包含了A中最小的i个元素,且A[1]<=A[2]<=…<=A[i-1]<=A[i]
终止:i=n+1,已知A[1…n]是A中最小的n个元素,且A[1]<=A[2]<=…<=A[n],得证。

在算法导论思考题2-2中又问了”冒泡排序和插入排序哪个更快“呢?

一般的人回答:“差不多吧,因为渐近时间都是O(n^2)”。
但是事实上不是这样的,插入排序的速度直接是逆序对的个数,而冒泡排序中执行“交换“的次数是逆序对的个数,因此冒泡排序执行的时间至少是逆序对的个数,因此插入排序的执行时间至少比冒泡排序快

三、选择排序

  • 特性:In-place sort,unstable sort。
  • 思想:每次找一个最小值。
  • 最好情况时间:O(n^2)。
  • 最坏情况时间:O(n^2)。

伪代码:

image

C++模板:

template <typename T>
void Selection_Sort(T *array, size_t length) {
    for (int i = 0; i != length; i++) {
        int min = i;
        for (int j = i + 1; j != length; j++) {
            if (array[min] > array[j]) {
                min = j;
            }
        }
        T temp = array[i];
        array[i] = array[min];
        array[min] = temp;
    }
}

证明算法正确性:

循环不变式:A[1…i-1]包含了A中最小的i-1个元素,且已排序。

初始:i=1,A[1…0]=空,因此成立。
保持:在某次迭代开始之前,保持循环不变式,即A[1…i-1]包含了A中最小的i-1个元素,且已排序,则进入循环体后,程序从 A[i…n]中找出最小值放在A[i]处,因此A[1…i]包含了A中最小的i个元素,且已排序,而i++,因此下一次循环之前,保持 循环不变式:A[1..i-1]包含了A中最小的i-1个元素,且已排序。
终止:i=n,已知A[1…n-1]包含了A中最小的i-1个元素,且已排序,因此A[n]中的元素是最大的,因此A[1…n]已排序,证毕。

四、归并排序

  • 特点:stable sort、Out-place sort
  • 思想:运用分治法思想解决排序问题。
  • 最坏情况运行时间:O(nlgn)
  • 最佳运行时间:O(nlgn)

分治法介绍:分治法就是将原问题分解为多个独立的子问题,且这些子问题的形式和原问题相似,只是规模上减少了,求解完子问题后合并结果构成原问题的解。
分治法通常有3步:Divide(分解子问题的步骤) 、 Conquer(递归解决子问题的步骤)、 Combine(子问题解求出来后合并成原问题解的步骤)。
假设Divide需要f(n)时间,Conquer分解为b个子问题,且子问题大小为a,Combine需要g(n)时间,则递归式为:
T(n)=bT(n/a)+f(n)+g(n)

算法导论思考题4-3(参数传递)能够很好的考察对于分治法的理解。

就如归并排序,Divide的步骤为m=(p+q)/2,因此为O(1),Combine步骤为merge()函数,Conquer步骤为分解为2个子问题,子问题大小为n/2,因此:
归并排序的递归式:T(n)=2T(n/2)+O(n)

而求解递归式的三种方法有:

  • (1)替换法:主要用于验证递归式的复杂度。
  • (2)递归树:能够大致估算递归式的复杂度,估算完后可以用替换法验证。
  • (3)主定理:用于解一些常见的递归式。

伪代码:

image

C++模板:

template <typename T>
void Merge(T *sourceArray, T *temp, int Start_Index, int Mid_Index, int End_Index) {
    int i = Start_Index, j = Mid_Index + 1, k = Start_Index;
    while (i != Mid_Index + 1 && j != End_Index + 1) {
        if (sourceArray[i] > sourceArray[j]) {
            temp[k++] = sourceArray[j++];
        } else {
            temp[k++] = sourceArray[i++];
        }
    }
    while (i != Mid_Index + 1) {
        temp[k++] = sourceArray[i++];
    }
    while (j != End_Index + 1) {
        temp[k++] = sourceArray[j++];
    }
    for (int i = Start_Index; i != End_Index + 1; i++) {
        sourceArray[i] = temp[i];
    }
}

template <typename T>
void Merge_Sort(T *sourceArray, T *temp, int Start_Index, int End_Index) {
    if (Start_Index < End_Index) {
        int Mid_Index = (Start_Index + End_Index) / 2;
        Merge_Sort(sourceArray, temp, Start_Index, Mid_Index);
        Merge_Sort(sourceArray, temp, Mid_Index + 1, End_Index);
        Merge(sourceArray, temp, Start_Index, Mid_Index, End_Index);
    }
}

C++ 链表的归并排序法:

// 链表的归并排序。
void LinkedList::sort(void) {
    if (this->size() > 1) {
        node* fast = this->head;
        node* slow = this->head;
        LinkedList li_left;
        LinkedList li_right;

        li_left.head = this->head;
        while (fast != NULL && fast->next != NULL) {
            li_left._size++;
            fast = fast->next->next;
            slow = slow->next;
        }
        li_left.tail = slow->prev;
        li_left.tail->next = NULL;

        li_right.head = slow;
        li_right.head->prev = NULL;
        li_right.tail = this->tail;
        li_right._size = this->_size - li_left._size;

        this->head = NULL;
        this->tail = NULL;

        li_left.sort();
        li_right.sort();

        node* pointer_left = li_left.head;
        node* pointer_right = li_right.head;

        node* pointer_head = NULL;
        node* pointer_tail = NULL;

        while (pointer_left != NULL && pointer_right != NULL) {
            node* temp;
            if (pointer_left->data <= pointer_right->data) {
                temp = pointer_left;
                pointer_left = pointer_left->next;
            } else {
                temp = pointer_right;
                pointer_right = pointer_right->next;
            }
            if (pointer_head == NULL) {
                pointer_head = pointer_tail = temp;
            } else {
                pointer_tail->next = temp;
                temp->prev = pointer_tail;
                pointer_tail = temp;
            }
            pointer_head->prev = NULL;
            pointer_tail->next = NULL;
        }

        while (pointer_left != NULL) {
            pointer_tail->next = pointer_left;
            pointer_left->prev = pointer_tail;
            pointer_tail = pointer_left;
            pointer_left = pointer_left->next;
        }

        while (pointer_right != NULL) {
            pointer_tail->next = pointer_right;
            pointer_right->prev = pointer_tail;
            pointer_tail = pointer_right;
            pointer_right = pointer_right->next;
        }

        this->head = pointer_head;
        this->tail = pointer_tail;

        li_left.head = li_left.tail = NULL;
        li_right.head = li_right.tail = NULL;
}

举例说明:

image

问:归并排序的缺点是什么?

答:他是Out-place sort,因此相比快排,需要很多额外的空间。

问:为什么归并排序比快速排序慢?

答:虽然渐近复杂度一样,但是归并排序的系数比快排大。

问:对于归并排序有什么改进?

答:就是在数组长度为k时,用插入排序,因为插入排序适合对小数组排序。在算法导论思考题2-1中介绍了。复杂度为O(nk+nlg(n/k)) ,当k=O(lgn)时,复杂度为O(nlgn)

五、快速排序

算法介绍:

设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。
一趟快速排序的算法是:
1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]和A[i]互换;
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。

QUICK_SORT(A,p,r)
if p<r
    then q ←PARTITION(A,p,r)
QUICKSORT(A,p,q-1)
QUICKSORT(A,q+1,r)
/*
为排序一个完整的数组A,最初的调用是QUICKSORT(A1,length[A])。
快速排序算法的关键是PARTITION过程,它对子数组A[p..r]进行就地重排:
*/
PARTITION(A,p,r)
    x←A[r]
    i←p-1
    for j←p to r-1
        do if A[j]≤x
            then i←i+1
        exchange A[i]←→A[j]
        exchange A[i+1]←→A[r]
return i+1[2] 

C++模板:

template <typename T>
void Quick_Sort(T *array, int Start_Index, int End_Index) {
    if (End_Index >= Start_Index) {
        int first = Start_Index;
        int last = End_Index;
        T key = array[first];
        while (first < last) {
            while (first < last && array[last] >= key) {
                last--;
            }
            array[first] = array[last];
            while (first < last && array[first] <= key) {
                first++;
            }
            array[last] = array[first];
        }
        array[first] = key;
        Quick_Sort(array, Start_Index, first - 1);
        Quick_Sort(array, first + 1, End_Index);
    }
}
  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值