常见内排序实现汇总(含部分优化实现,基于链表的实现),以及性能比较
这篇文章中包含了对一部分排序的实现,以及常见排序算法性能的比较。
插入排序(Insert Sort)部分
1. 折半插入排序(Binary Insert Sort)实现
2. 插入排序的链表实现
(1) 改变结点值,不改变结点指针
(2) 改变结点指针,不改变结点值
(3) 上面(2) 的简洁版
归并排序(Merge Sort)部分
1. 归并排序实现
2. 归并排序的优化,归并部分不需要判断是否到达末尾。
3. 归并排序的基于链表实现
当我们需要对链表进行排序时,由于不能对它的元素进行随机访问,所以更适合使用归并排序
快速排序(Quick Sort)部分
1. 快速排序实现
2. 快速排序在pivot选择上的优化,从而让划分的子序列长度尽量相等。
堆排序(Heap Sort)部分
1. 堆排序实现
测试Main函数
代码:
/** * copyright: Felix Fang @ cnblogs */ #include <stdio.h> //定义链表和相关函数以做备用 struct ListNode{ int m_value; ListNode *m_next; }; ListNode* CreateListNode(int value) { ListNode* pNode = new ListNode(); pNode->m_value = value; pNode->m_next = NULL; return pNode; } void ConnectListNodes(ListNode* pCurrent, ListNode* pNext) { if(pCurrent == NULL) { printf("Error to connect two nodes.\n"); return; } pCurrent->m_next = pNext; } void PrintList(ListNode* pHead) { printf("PrintList starts.\n"); ListNode* pNode = pHead; while(pNode != NULL) { printf("%d\t", pNode->m_value); pNode = pNode->m_next; } printf("\nPrintList ends.\n"); } /** Binary Insert sort 在搜索插入位置使用二分搜索法减小时间复杂度 */ template<typename T> void BinaryInsertSortIncrease(T* list, int size){ if(NULL == list) return; for(int i = 1;i < size;i++){ T temp = list[i]; int start = 0; int end = i-1; //开始遍历0到i-1 while(start < end){ int mid = (start + end)/2; if(temp < list[mid]) end = mid; //end 必须赋值为mid 而不是mid - 1,考虑sart = 0, end = 1的情况,此时进入这行代码,若end = mid-1,则end = -1,后面给temp腾位置时,会把list[-1] 付给 list[0] else if(temp > list[mid]) start = mid + 1; else{ end = mid; break; } } if(list[end] < temp) end++; //不是以beak退出循环时,start == end,这时候再和temp比较一次 for(int k = i; k > end; k--){ list[k] = list[k-1]; } list[end] = temp; } } /** Insert sort based on linked list, change the data of node */ void InsertSortLinkedListIncrease(ListNode *head){ if(NULL == head) return; ListNode *temp = head -> m_next; //从第二个节点开始处理 while(temp != NULL){ ListNode *p = head; while(p -> m_value < temp -> m_value && p != temp){ p = p -> m_next; //在前面已排好序的结点中寻找插入位置 } int moveTempValue = p -> m_value; //开始腾地方,因为是单向链表,只能从前往后遍历,腾地方的时候需要定义一个缓存结点值 p -> m_value = temp -> m_value; while(p != temp){ int tempvalue = p -> m_next -> m_value; p -> m_next -> m_value = moveTempValue; //把缓存结点值(里面存的是p结点原本的值)赋值到 p -> m_next -> m_value moveTempValue = tempvalue; //把 p -> m_next -> m_value原本的值 放到缓存结点。 p = p -> m_next; } temp = temp -> m_next; //处理下一个结点 } } /** Insert sort based on linked list, change the link of nodes rather than data Idea: Divide the orginal linked list into two list, the first one is sorted, second is rest part. Everytime we take the first node from the second list, then find a proper place in first list to insert it. When the second list becomes empty, the first list is the sorted list. */ ListNode * InsertSortLinkedListIncrease_link(ListNode *head){ //注意这次因为链表头结点会更改,因此不能为void类型 if(NULL == head) return NULL; ListNode* head1 = head; ListNode *pre = head; ListNode* temp2 = head1 -> m_next; ListNode* toMove = NULL; head1 -> m_next = NULL; ListNode *temp1 = NULL; while(temp2 != NULL){ if(pre -> m_value > temp2 -> m_value){ //考虑需要插到pre前的情况 head1 = temp2; head1 -> m_next = pre; temp2 = temp2 -> m_next; }else{ while(temp1 != NULL && temp1 -> m_value < temp2 -> m_value){ temp1 = temp1 -> m_next; //pre和temp1两个指针同步移动 pre = pre -> m_next; } toMove = temp2; temp2 = temp2 -> m_next; //第二个链表继续往下遍历 pre -> m_next = toMove; //将这个节点放到第一个链表中,pre和temp1之间 toMove -> m_next = temp1; } pre = head1; temp1 = pre -> m_next; } return head1; } //更加简洁的写法,用for代替了内层while ListNode * InsertSortLinkedListIncrease_linkMoreBrief(ListNode *head){ if(NULL == head) return NULL; ListNode* head1 = head; ListNode *pre = head; ListNode* temp2 = head1 -> m_next; ListNode* toMove = NULL; head1 -> m_next = NULL; ListNode *temp1 = NULL; while(temp2 != NULL){ for(temp1 = head1; temp1 != NULL && temp1 -> m_value < temp2 -> m_value; pre = temp1, temp1 = temp1 -> m_next); if(temp1 == head1){ head1 = temp2; temp2 = temp2 -> m_next; head1 -> m_next = temp1; }else{ toMove = temp2; temp2 = temp2 -> m_next; pre -> m_next = toMove; toMove -> m_next = temp1; } } return head1; } /** Merge sort 归并排序的最好最坏时间复杂度都是O(nlogn),而且稳定 */ template <typename T>void MergeSortCore(T *list1, T *list2, int start, int end); template <typename T>void MergeSortCoreImprove(T *list1, T *list2, int start, int end); template <typename T>void MergeSort(T *list1, int size){ T *list2 = new T[size]; MergeSortCoreImprove(list1, list2, 0, size); } template <typename T>void MergeSortCore(T *list1, T *list2, int start, int end){ if(NULL == list1) return; if(end - start == 1) return; //这一句不可省略,否则 mid==start,进入无限循环 int mid = (start + end)/2; MergeSortCore(list1, list2, start, mid); MergeSortCore(list1, list2, mid, end); //划分子序列,star 到 mid,mid到end分别为两个子序列 for(int i = start;i < end;i++){ list2[i] = list1[i]; //将结果放到list2中,list2作为缓存区,暂存两个子序列内的值。 } int j = start, k = mid, ite = start; while(j < mid && k < end){ if(list2[j] < list2[k]){ list1[ite++] = list2[j++]; }else{ list1[ite++] = list2[k++]; } } while(j < mid) list1[ite++] = list2[j++]; while(k < end) list1[ite++] = list2[k++]; } /** Merge sort improvement 改进版本的归并排序,这个方法可以省去归并最后两步:检测两个子序列中是否有一个还未遍历到末尾。从而提高一些效率。 方法由R.Sedgewick提出 改进的方法是,从list1向 list2复制时,第二个子序列是反向复制的,归并时,第二个子序列也是从末尾遍历到最前进行遍历。 当子序列A到达其末尾时,会因为++再进一位,从而进入另一个子序列B的末尾,这时候它就比B中还未遍历的值都大,所以再往下就会把B中剩余的值复制进去。 */ template <typename T>void MergeSortCoreImprove(T *list1, T *list2, int start, int end){ if(NULL == list1) return; if(end - start == 1) return; int mid = (start + end)/2; MergeSortCoreImprove(list1, list2, start, mid); MergeSortCoreImprove(list1, list2, mid, end); //从开始到这部分都和 MergeSortCore一样 int i = 0; for(i = start; i < mid; i++) list2[i] = list1[i]; for(i = mid; i < end; i++) list2[i] = list1[end + mid - i - 1]; //序列list2反向复制 int j = start, k = end - 1, ite = start; while(ite < end){ if(list2[j] < list2[k]){ list1[ite++] = list2[j++]; }else{ list1[ite++] = list2[k--]; //从两边往中间比较 } } } /** Merge sort based on linked list */ //用于把链表划分为基本相等的两段。遇到空链表或者单节点链表直接返回。 //划分办法为定义两个指针,一个步长1,一个步长2。 ListNode * DivideLinkedList(ListNode *head){ if(head == NULL || head -> m_next == NULL){ return NULL; } ListNode *tempHead = new ListNode(); tempHead -> m_next = head; //定义虚头结点,步长1的指针从这里出发,这样才能保证对于结点数为2的链表,步长1的指针最终落在第一个结点上。 ListNode *step1 = tempHead; ListNode *step2 = head; for(; step2 != NULL; step1 = step1 -> m_next){ step2 = step2 -> m_next; if(step2 != NULL) step2 = step2 -> m_next; } ListNode *temp = step1; temp = temp -> m_next; step1 -> m_next = NULL; return temp; } //这里的Merge采用的是将List2 merge到 List1里的方式,方法有点像前面的InsertSortLinkedListIncrease_linkMoreBrief, //不同的是,这里temp1不需要每次for都要赋初始值。要注意的是:如果list2开始的值都比list1小而且递减(比如 list1: 2,3; list2: -1, -2),merge到list1中时,每次都是加到头结点之前。 //这样就需要修改头结点,temp1也要赋值为新的头结点。 ListNode* MergeLinkedList(ListNode* head1, ListNode* head2){ ListNode* pre = NULL; ListNode* temp1 = head1; ListNode* temp2 = head2; ListNode* newHead = head1; while(temp2 != NULL){ for(; temp1 != NULL && temp1 -> m_value < temp2 -> m_value; pre = temp1, temp1 = temp1 -> m_next); ListNode *toMove = temp2; temp2 = temp2 -> m_next; if(temp1 == newHead){ toMove -> m_next = newHead; newHead = toMove; temp1 = newHead; //调试查出错误后加上,因为头结点被取代了,所以temp1必须手动赋值为新的头结点。 }else{ pre -> m_next = toMove; toMove -> m_next = temp1; } } return newHead; } ListNode* MergeSortLinkedListIncrease(ListNode* head){ if(NULL == head) return NULL; if(head -> m_next == NULL) return head; ListNode* secHead = DivideLinkedList(head); ListNode* firstHead = MergeSortLinkedListIncrease(head); secHead = MergeSortLinkedListIncrease(secHead); return MergeLinkedList(firstHead, secHead); } /** Quick sort 就平均计算时间而言,快速排序是常见内排序算法中最好的。 但在最坏情况(序列已经由小到大排好序),快速排序的递归会成为单支树,每次划分只得到比上次少一个元素的序列。 速度也是O(n^2),而且还慢于插入排序(人家插入排序还能用二分法定位加快速度呢)。 如何才能改进呢? 快排序之所以在对于已经基本有序的序列效率低,是因为我们在实现算法时,取得pivot值是最左边的点(取最右边的也一样),这样pivot基本就是这个序列中最大或者最小值。 导致以这个pivot为基准时,划分出来的两个子序列长度严重不均等。只有当两个子序列基本长度均等,排序的时间才会以log2递减。 那么如何取一个较为合适的pivot呢? 我们本来是将start的值作为中间值,现在我们取 start, end, middle三个地方的值的中位数,把它和left交换下,然后继续把left当作pivot。 */ template <typename T> void QuickSortIncreaseCore(T* list, int start, int end); template <typename T> void QuickSortIncreaseCoreImprove(T* list, int start, int end); template <typename T> void InnerChange(T* list, int left, int right){ T temp = list[left]; list[left] = list[right]; list[right] = temp; } template <typename T> void QuickSortIncrease(T* list, int size){ QuickSortIncreaseCoreImprove(list, 0, size - 1); } template <typename T> void QuickSortIncreaseCore(T* list, int start, int end){ if(NULL == list) return; if(start >= end) return; T pivot = list[start]; int i = start, j = end + 1; do{ do i++; while(list[i] < pivot && i <= end); do j--; while(list[j] > pivot && j >= start); if(i < j) InnerChange(list, i, j); }while(i < j); InnerChange(list, start, j); //此处只能用j不能用i, 考虑{1, 2},此时pivot=1,如果用i, 会导致调用Innerchange(list, 0, 1),结果成了{2, 1} QuickSortIncreaseCore(list, start, j-1); QuickSortIncreaseCore(list, j+1, end); } /** Quick sort improvement */ //获取start, end, middle 三个值的中位数所在的位置 template <typename T> int GetMidianPlace(T* list, int start, int end){ int mid = (start + end)/2; int MedianPlace = 0; T tempSmallValue = list[start] < list[mid] ? list[start] : list[mid]; int tempSmallPlace= list[start] < list[mid] ? start : mid; MedianPlace = tempSmallValue > list[end] ? tempSmallPlace : -1; if(MedianPlace < 0){ tempSmallPlace = tempSmallPlace == mid ? start : mid; MedianPlace = list[end] < list[tempSmallPlace] ? end : tempSmallPlace; } return MedianPlace; } template <typename T> void QuickSortIncreaseCoreImprove(T* list, int start, int end){ if(NULL == list) return; if(start >= end) return; int midianPlace = GetMidianPlace(list, start, end); InnerChange(list, midianPlace, start); T pivot = list[start]; int i = start, j = end + 1; do{ do i++; while(list[i] < pivot && i <= end); do j--; while(list[j] > pivot && j >= start); if(i < j) InnerChange(list, i, j); }while(i < j); InnerChange(list, start, j); //此处只能用j不能用i, 考虑{1, 2},此时pivot=1,如果用i, 会导致调用Innerchange(list, 0, 1),结果成了{2, 1} QuickSortIncreaseCore(list, start, j-1); QuickSortIncreaseCore(list, j+1, end); } /** Heap Sort 堆排序最大的优点在于:一、最坏情况和最好情况下时间复杂度均为 O(nlogn),二、使用的额外空间为O(1),所有的调整都是在list内部 缺点是不能应用在链表上,因为需要大量的非连续访问。 */ //Ajust函数,当左右子树都为最大堆时,adjust可以将root, 左右子树组成的新树调整为最大堆。 template <typename T>void adjust(T* list, int root, int size){ int j = root; while((j * 2 + 1 )< size){ int tempRoot = j; j = j * 2 + 1; if(j+1 < size && list[j+1] > list[j]) j++; if(list[j] > list[tempRoot]){ InnerChange(list, j, tempRoot); }else{ break; } } } template <typename T>void HeapSortIncrease(T* list, int size){ if(NULL == list) return; if(size <= 1) return; for(int i = (size - 2)/2; i >= 0;i--)//这部分for的时间复杂度为O(n/2logn) adjust(list, i, size); //先将现有的数组调整成一个最大堆,从号码最大的非叶节点开始,逐次往前调用adjust 即可。 for(int j = size-1;j > 0;j--){ InnerChange(list, j, 0); //每次将最大堆的根结点提出,和最末的结点交换位置。 adjust(list, 0, j); //将除最末结点外的部分adjust成新的最大堆。这部分for的时间复杂度为O(nlogn) } } int main(){ int i = 0; //测试二分插入排序 /*int test[10] = {3, 6, 5, 2, 6, 12, 16, 9, 7, 8}; int test2[5] = {100, 90, 70, 60, 50}; int test3[1] = {1}; BinaryInsertSortIncrease(test, 10); for(i = 0;i < 10;i++){ printf("%d ", test[i]); } printf("\n"); BinaryInsertSortIncrease(test2, 5); for(i = 0;i < 5;i++){ printf("%d ", test2[i]); } printf("\n"); BinaryInsertSortIncrease(test3, 1); for(i = 0;i < 1;i++){ printf("%d ", test3[i]); } printf("\n");*/ //测试基于链表的插入排序,改变结点值 /*ListNode* pNode1 = CreateListNode(1); ListNode* pNode2 = CreateListNode(6); ListNode* pNode3 = CreateListNode(5); ListNode* pNode4 = CreateListNode(2); ListNode* pNode5 = CreateListNode(8); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); PrintList(pNode1); InsertSortLinkedListIncrease_link(pNode1); PrintList(pNode1); InsertSortLinkedListIncrease_link(NULL); ListNode* pNode21 = CreateListNode(5); InsertSortLinkedListIncrease_link(pNode21); PrintList(pNode21);*/ //测试基于链表的插入排序,改变结点指针 /*ListNode* lNode1 = CreateListNode(2); ListNode* lNode2 = CreateListNode(5); ListNode* lNode3 = CreateListNode(7); ListNode* lNode4 = CreateListNode(3); ListNode* lNode5 = CreateListNode(2); ConnectListNodes(lNode1, lNode2); ConnectListNodes(lNode2, lNode3); ConnectListNodes(lNode3, lNode4); ConnectListNodes(lNode4, lNode5); PrintList(lNode1); lNode1 = InsertSortLinkedListIncrease_link(lNode1); PrintList(lNode1); InsertSortLinkedListIncrease_link(NULL); ListNode* lNode21 = CreateListNode(5); lNode21 = InsertSortLinkedListIncrease_link(lNode21); PrintList(lNode21);*/ //测归并排序 /*int test31[10] = {3, 6, 5, 2, 6, 12, 16, 9, 7, 8}; int test32[5] = {90, 100, 70, 60, 50}; int test33[1] = {1}; MergeSort(test31, 10); for(i = 0;i < 10;i++){ printf("%d ", test31[i]); } printf("\n"); MergeSort(test32, 5); for(i = 0;i < 5;i++){ printf("%d ", test32[i]); } printf("\n"); MergeSort(test33, 1); for(i = 0;i < 1;i++){ printf("%d ", test33[i]); } printf("\n");*/ //测试基于链表的归并排序 /*ListNode* lNode41 = CreateListNode(2); ListNode* lNode42 = CreateListNode(3); ListNode* lNode43 = CreateListNode(1); ListNode* lNode44 = CreateListNode(-1); ListNode* lNode45 = CreateListNode(-2); ConnectListNodes(lNode41, lNode42); ConnectListNodes(lNode42, lNode43); ConnectListNodes(lNode43, lNode44); ConnectListNodes(lNode44, lNode45); PrintList(lNode41); lNode41 = MergeSortLinkedListIncrease(lNode41); PrintList(lNode41); MergeSortLinkedListIncrease(NULL); ListNode* lNode431 = CreateListNode(5); lNode431 = MergeSortLinkedListIncrease(lNode431); PrintList(lNode431);*/ //测试快速排序 /*int test5[10] = {3, 6, 5, 2, 6, 12, 16, 9, 7, 2}; int test52[5] = {100, 90, 70, 60, 50}; int test53[1] = {1}; QuickSortIncrease(test5, 10); for(i = 0;i < 10;i++){ printf("%d ", test5[i]); } printf("\n"); QuickSortIncrease(test52, 5); for(i = 0;i < 5;i++){ printf("%d ", test52[i]); } printf("\n"); QuickSortIncrease(test53, 1); for(i = 0;i < 1;i++){ printf("%d ", test53[i]); } printf("\n");*/ //测试堆排序 int test6[10] = {3, 6, 5, 2, 6, 12, 16, 9, 7, 2}; int test62[5] = {100, 50, 70, 60, 50}; int test63[1] = {1}; HeapSortIncrease(test6, 10); for(i = 0;i < 10;i++){ printf("%d ", test6[i]); } printf("\n"); HeapSortIncrease(test62, 5); for(i = 0;i < 5;i++){ printf("%d ", test62[i]); } printf("\n"); HeapSortIncrease(test63, 1); for(i = 0;i < 1;i++){ printf("%d ", test63[i]); } printf("\n"); }
关于排序算法的稳定性
稳定性是指值相等的结点在排序后相对位置没有被打乱。A==B,在原序列中A在B前,排序后A依然在B前面。
稳定排序算法的优势:可以基于不同的key对序列进行多次排序。比如数据库中可以基于多个列作Index,假设列A和列B,先基于列A作排序,排序完后再基于列B做排序。如果第二次排序时我们选择的算法是稳定的,我们就可以使数据按照B列上的顺序排列,对于B列上的值相等的数据,他们仍会按照A列上的值顺序排列。在数据库中查找数据时,基于这两个列的Index就发挥作用了。
具体关于排序算法性能的比较,参见下图:
图片来自 http://blog.csdn.net/winniepu/article/details/3928363
快速排序辅助存储空间有误,应该为 O(logn)~O(n)。
本文代码部分为原创,部分优化思路参考自《数据结构》第二版,殷人昆主编,清华大学出版社,第9章 排序。
------------------------------------------------
Felix原创,转载请注明出处,感谢博客园!
posted on 2014-02-03 11:47 Felix Fang 阅读(1744) 评论(0) 编辑 收藏 举报