归并排序(求逆序数)、删除链表中的重复结点(善用辅助结点)

本文详细介绍了归并排序的原理,包括其空间复杂度和时间复杂度,并通过代码展示了如何实现归并排序。此外,还探讨了如何在归并排序过程中计算逆序数。另一方面,文章通过示例解释了如何利用辅助结点删除链表中的重复元素,确保链表中每个元素都是唯一的。这两种算法都展示了在处理数据结构时的有效策略。
摘要由CSDN通过智能技术生成

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
015result += groupCount0
035result += groupCount0
175/0
276result += groupCount2
289result += groupCount4
所以,通过以上的算法,可知通过合并这一组,逆序数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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值