1.归并排序
空间复杂度O(n)
时间复杂度O(nlogn)
归并排序分为两步,第一步是分组,最开始将每个数一组。
第二步是合并。所谓一次合并就是每两个一组,设两个指针,将其合并成一个新组。例如图中{2,5,9}一组,{7,12}一组,2和7比较,2小,建立一个新组,2先放进去,然后5和7比较,5小,5放进新组,9和7比较,7小,7放入新组。以此类推,合并后的新组就是{2,5,7,9,12}。
空间复杂度:O(n),需要引入一个长度为n的tmpArray来用来表示每次合并后的新组,但tmpArray这个数组并不是每次合并全部元素均有用,比如合并{3,4}{1,11}这两组,tmpArray只有下标为5-8的元素有用,用来记录合并后的新组{1,3,4,11},长度设置为n,是为了方便最后一次合并,最后一次合并,新组的长度为n。
时间复杂度:O(nlogn),log(n)是合并了logn轮。假设要排序8个数,第一轮合并成了4组,第二轮合并成了2组,第三轮合并成了1组,共合并了3轮,即log2(8),是对数阶。n是每轮合并操作的时间复杂度。每轮操作,都需访问要排序的所有元素一次。
代码实现:
1.为tmpArray分配空间
void MergeSort(int arr[],int n){
int* tmpArray = (int*)malloc(sizeof(int) * n);
if(tmpArray){
MyMerge(arr,tmpArray,n,0,n-1);
}else{
printf("error,can not allocate helper array!");
}
}
2.用递归的方法进行分组和合并
void MyMerge(int arr[],int tmpArray[],int n,int left,int right){
//当left = right时,不需要再分割。
if(left < right){
int mid = (left+right)/2;
MyMerge(arr,tmpArray,n,left,mid);
MyMerge(arr,tmpArray,n,mid+1,right);
//对已经排好序的左半部和右半部进行合并
MyConcat(arr,tmpArray,left,mid,right);
}
}
即先对左半部分进行划分,后对右半部分进行划分,最后将这两部分合并。
3.写合并函数,使合并后的新组元素依然从小到大。
void MyConcat(int arr[],int tmpArray[],int left,int mid,int right){
//定义左半部分指针
int l_pos = left;
//定义右半部分指针
int r_pos = mid+1;
//定义合并后的新数组指针
int cur = left;
//合并操作
while((l_pos!=mid+1)||(r_pos!=right+1)){
if(l_pos == mid+1){ //如果左半部元素已合并完成,就将已有序的右半部合并到新组。
tmpArray[cur++] = arr[r_pos++];
}else if(r_pos == right+1){ //如果右半部元素已经合并完成,就将左半部元素合并到新组。
tmpArray[cur++] = arr[l_pos++];
}else{
tmpArray[cur++] = (arr[l_pos] > arr[r_pos])?arr[r_pos++]:arr[l_pos++];
}
}
//拷贝到原数组
for(int i=left;i<=right;i++){
arr[i] = tmpArray[i];
}
}
[调用及结果]
int main() {
int arr[] = {9,12,3,4,6,1};
int n = 6;
MergeSort(arr,n);
PrintArr(arr,n);
return 0;
}
求逆序数的操作只需在此基础上更改一步:
定义一个int变量result用于记录计算的逆序数对数。
然后修改MyConcat也就是每次合并出新组的这个函数如下:
void MyConcat(int arr[],int tmpArray[],int left,int mid,int right,int& result){
int groupCount = 0; //用于记录每次合并的逆序数
//定义左半部分指针
int l_pos = left;
//定义右半部分指针
int r_pos = mid+1;
//定义合并后的新数组指针
int cur = left;
//合并操作
while((leftIndex<=mid)||(rightIndex<=right)){
if(leftIndex > mid){
tempArr[cur++] = arr[rightIndex++];
}else if(rightIndex > right){
tempArr[cur++] = arr[leftIndex++];
result += groupcount;
}else{
if(arr[leftIndex] < arr[rightIndex]){
tempArr[cur++] = arr[leftIndex++];
result += groupcount;
}else{
tempArr[cur++] = arr[rightIndex++];
groupcount += 1;
}
}
}
//拷贝到原数组
for(int i=left;i<=right;i++){
arr[i] = tmpArray[i];
}
}
例如合并左组{1,3,7,8}和右组{5,6,9},过程如下:
groupCount | 左指针 | 右指针 | result操作 | result |
---|---|---|---|---|
0 | 1 | 5 | result += groupCount | 0 |
0 | 3 | 5 | result += groupCount | 0 |
1 | 7 | 5 | / | 0 |
2 | 7 | 6 | result += groupCount | 2 |
2 | 8 | 9 | result += groupCount | 4 |
所以,通过以上的算法,可知通过合并这一组,逆序数result加了4。 | ||||
在归并排序的过程中,累计result,即可得到逆序对之和。 |
2.善用辅助结点
先看一道例题
ListNode* deleteDuplicates(ListNode* head) {
// write code here
if(head == NULL){
return NULL;
}else{
ListNode* headcpy = head;
int currentNum = headcpy->val;
while(headcpy->next!=NULL){
if(headcpy->next->val!=currentNum){
headcpy = headcpy->next;
currentNum = headcpy->val;
}else{
headcpy->next = headcpy->next->next;
}
}
return head;
}
}
思路:利用currentNum记录当前结点的值,遍历这个链表,当链表中下一个结点的值等于currentNum,则做head->next = head->next->next操作,隔过这个结点,判断下一个结点。当下一个结点的值不等于currentNum时,head向后遍历,并更新currentNum.
对于这道题的返回值,只需在开始遍历链表前记录首结点即可,因为操作前后首结点一定不会发生改变。
但再看下面这道题:
这道题操作前后的首结点就不一定相同了,假如一上来就是重复元素,一定不同。如果分情况讨论,不是好办法。这时候就要用到辅助结点了。
ListNode* deleteDuplicates(ListNode* head) {
// write code here
ListNode* helper = new ListNode(1001); //辅助结点
ListNode* cur = helper;
ListNode* pre = head; //用于连接。
ListNode* traverse = head;
int value = 1001;
int count = 0;
while(traverse!=NULL){
if(value != traverse->val){
if(count==1){
cur->next = pre;
cur = cur->next;
}
value = traverse->val;
count = 1;
pre = traverse;
traverse = traverse->next;
}else{
count++;
pre = traverse;
traverse = traverse->next;
}
}
if(count == 1){
cur->next = pre;
cur = cur->next;
}
cur->next = NULL;
return helper->next;
}
其中的count用于计数结点值相同的数量,当遇到不同值的结点时,查看count,如果count == 1,说明该结点的值在链中唯一,则将其链接到helper链上,如果count>1,则说明该结点的值在链中不唯一,不连接到helper链上,继续遍历该链表。
值得注意的是:这里的count初值,不能赋为1,如果赋为1,不论首结点的元素是否在链表中重复,其均会被链接到helper链上,造成错误。这里设成0,就没有这个问题了。
仔细品味这个题,利用一个helper辅助结点,省去了不少麻烦事,即我们不用管第一个结点是否重复。因为不知道第一个元素是否重复,所以我们不知道删去重复元素后的链表中的首结点究竟是哪个,既然不知道,就先建立一个辅助结点helper,等到链表遍历完成,自然也就知道了,这时再返回删去重复元素后的首结点。不要忘记最后返回结果的时候返回的是helper->next,而不是helper。