归并排序:
给定一个int数组A以及大小n,请返回排序后的数组;
时间复杂度分析:
T(n) 拆分 n/2, 归并 n/2 ,一共是n/2 + n/2 = n
/ \ 以下依此类推:
T(n/2) T(n/2) 一共是 n/2*2 = n
/ \ / \
T(n/4) ........... 一共是 n/4*4 = n
一共有logn层,故时间复杂度是 O(nlogn)
空间复杂度:就是那个临时的数组和递归时压入栈的数据占用的空间:n + logn;所以空间复杂度为: O(n)
核心思想:
是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并排序的一般步骤为:
1)将待排序数组(链表)取中点并一分为二;
2)递归地对左半部分进行归并排序;
3)递归地对右半部分进行归并排序;
4)将两个半部分进行合并(merge),得到结果。
采用递归的过程进行归并排序,并使用一个与A大小相同的辅助数组空间B,大家都知道递归是一个进栈和出栈的过程。
1、递归调用归并排序过程将数组折半查找:
1.1、递归调用数组A前一半,和后一半,返回条件是开始索引>=结束索引
(递归调用这一步是压栈过程,直到每一半的大小划分为1)
1.2、调用将前一半、后一半的数组,归并并且排好序成为一个数组的函数merger()。
(这一步就是出栈,并且从划分为1的划分慢慢变大,但是划分的数组因为调用merger()函数已经成为有序数组)(这个是重点理解)
2、merger()函数归并思想体现在这(将前一半a1数组和后一半a2数组,合并并排序,其实a1和a2经过前面的递归已经有序):
2.1、将a1和a2从头开始比较,谁小,就把谁存到B数组里面(重点)
2.2、将a1或a2中还没比较的,直接复制到数组B中
2.3、将排序好的B数组复制给A数组
A[i]和A[j]进行比较谁小,就把它放到B中,然后小的下标向后移动,另一个不动,循环比较;
直到有一个先结束(A[j]先结束),另外一个(A[i]的6,7值)还没结束,直接将没结束的后面每比较的直接复制到B中。
代码:
class MergeSort {
public:
//第一步:分配B数组
int* mergeSort(int* A, int n) {
int *B = (int*)malloc(sizeof(int) * n);
QuickSort(A, B, 0, n-1);
free(B);
return A;
}
//第三步:merger()函数
void merger(int *A, int *B, int start, int mid, int end){
int i = start;
int j = mid+1;
int k = start;
//将a1和a2从头开始比较,谁小,就把谁存到B数组里面
while(i <=mid && j <=end){
if(A[i] < A[j]){
B[k++] = A[i++];
}
else{
B[k++] = A[j++];
}
}
//将a1或a2中还没比较的,直接复制到数组B中
while(i != mid+1){
B[k++] = A[i++];
}
while(j != end+1){
B[k++] = A[j++];
}
//将排序好的B数组复制给A数组
k=start;
while(k <= end){
A[k++] = B[k];
//k++; = 为从右到左的运算 不能写成 A[k] = B[k++],因为这样k不是从0开始,k++已经让k+1过了A[k]中的k是1;
}
}
//第二步:递归调用归并排序
void QuickSort(int *A, int *B, int start, int end){
if(start >= end)
return;
int mid = (start + end) /2;
QuickSort(A, B, start, mid);
QuickSort(A, B, mid+1, end);
merger(A, B, start, mid, end);
}
};
main调用:
#include <iostream>
using namespace std;
int main(){
int AAA[]={1,3,6,9,2,5,4,8,7};
MergeSort M;
int *b=M.mergeSort(AAA,9);
for(int i=0;i<9;i++){
cout<<b[i]<<endl;
}
return 0;
}
链表:
采用快慢指针找中点:快慢指针思路,快指针一次走两步,慢指针一次走一步,快指针在链表末尾时,慢指针恰好在链表中点
时间复杂度为:O(nlogn),原因一样
空间复杂度为:O(1),因为没有借助其他空间。
代码:
class Solution {
public:
ListNode* findMiddle(ListNode* head){
ListNode* chaser = head;
ListNode* runner = head->next;//一定要用next
while(runner != NULL && runner->next != NULL){
chaser = chaser->next;
runner = runner->next->next;
}
return chaser;
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1 == NULL){
return l2;
}
if(l2 == NULL){
return l1;
}
ListNode* dummy = new ListNode();//空结构
ListNode* head = dummy;
while(l1 != NULL && l2 != NULL){
if(l1->val > l2->val){
head->next = l2;
l2 = l2->next;
}
else{
head->next = l1;
l1 = l1->next;
}
head = head->next;
}
if(l1 == NULL){
head ->next = l2;
}
if(l2 == NULL){
head->next = l1;
}
return dummy->next;
}
ListNode* sortList(ListNode* head) {
if(head == NULL || head ->next == NULL){
return head;
}
ListNode* middle = findMiddle(head);
ListNode* right = sortList(middle->next);//这就是上面为啥要用next的原因
middle -> next = NULL;//截断
ListNode* left = sortList(head);
return mergeTwoLists(left, right);
}
};
main()调用
#include <iostream>
using namespace std;
struct ListNode{
int val;
struct ListNode* next;
};
int main(){
ListNode *list=(ListNode *)malloc(sizeof(ListNode));
list->val=2;
list->next=NULL;
//可以用循环
ListNode *l=list;
ListNode *p=(ListNode *)malloc(sizeof(ListNode));
p->val=4;
list->next=p;
list=list->next;
ListNode *lo=(ListNode *)malloc(sizeof(ListNode));
lo->val=5;
list->next=lo;
list=list->next;
ListNode *q=(ListNode *)malloc(sizeof(ListNode));
q->val=3;
list->next=q;
list=list->next;
list->next=NULL;
Solution s;
ListNode* ss =s.sortList(l);
while(ss!=NULL){
cout<<ss->val<<endl;
ss=ss->next;
}
return 0;
}