88.合併兩個有序數組(LeetCode )——C語言

合併兩個有序數組,根據題目的條件不一樣,細節處理也不一樣。我想先單純的討論下合併兩個有序數組,不作時間及空間複雜度的限制。算法

只合並兩個有序數組
這裏,只合並兩個有序數組,不是徹底在LeetCode88題的條件上解題,我只是想挖掘多一點的思路。在這裏我只合併兩個有序數組爲一個有序數組,沒有其餘條件限制,後面將討論LeetCode88題的解法。 高手可略過。
有兩種思路,一種是先合併數組,而無論它是否有序,而後將合併以後的數組排序。另外一種思路是,在合併數組的過程當中,對元素進行排序,合併結束,排序也結束,這種思路借鑑了歸併排序的歸併過程,即它用到了數組有序這個條件。

1、先將兩個數組合並,而後再排序。數組

開闢一個新數組來存儲這兩個數組中的元素,須要O(m+n)的空間複雜度。將兩個數組合並,此處不考慮這兩個數組有序,所以合併數組時間花費爲O(m+n),而後排序數組(各類排序均可),這裏考慮想要較好的時間複雜度,所以用快速排序,時間複雜度爲O((m+n)lg(m+n)),綜合起來,最終的時間複雜度爲O((m+n)lg(m+n)),空間複雜度O(m+n)。函數

#include <stdio.h>
#include <stdlib.h>

void exchange(int* x, int* y)
{
  int temp = *x;
  *x = *y;
  *y = temp;
}

void mergeArr(int *arr, int len, int *arr1, int len1, int *arr2, int len2)
{
  for (int i = 0; i < len; i++)
  {
    if (i < len1)
    {
      arr[i] = arr1[i];
    }
    else
    {
      arr[i] = arr2[i - len1];
    }
  }
}

// 快速排序是一種原址排序,不須要分配新的數組空間
// 只須要分配常量空間來存儲主元以及用於數組元素交換
// start和end爲子數組下標的起始位置與終止位置
// 快速排序的分割數組步驟,這是快速排序的核心
int partition(int *arr, int start, int end)
{
  // 獲取主元
  int key = arr[end];
  // left不存在,故將i取值爲start-1
  int i = start - 1;
  for (int j = start; j < end; j++)
  {
    // 若是arr[j]小於主元,則將i遞增,那麼左邊小於主元的數組的
    // 長度增長了1,同時將arr[i]與arr[j]交換。
    // 由於在i遞增以後,指向的是一個大於主元的值
    // 而arr[j]則是一個小於主元的值,所以交換他們的值,交換完成
    // 則i及i以前的元素都是小於主元的值,i+1到j之間則是大於主元的值
    // j到end-1之間是不肯定大小的值,end即主元
    if (arr[j] <= key)
    {
      i = i + 1;
      exchange(arr + i, arr + j);
    }
  }
  exchange(arr + i + 1, arr + end);
  return i + 1;
}
// 快排也用到了分治的思想,即將大問題分割爲性質相同的小問題,
// 小問題繼續分割爲更小的問題,一直到不能再分割,
// 而後將小問題的解合併起來,就得到最終的結果
// 所以,快排是先分割,而後在子數組上遞歸調用本身
void quickSort(int *arr, int start, int end)
{
  if (start < end)
  {
    int mid = partition(arr, start, end);
    quickSort(arr, start, mid - 1);
    quickSort(arr, mid + 1, end);
  }
}

void mergeArrSorted(int *arr, int len, int *arr1, int len1, int *arr2, int len2)
{
  mergeArr(arr, len, arr1, len1, arr2, len2);
  printf("merge Array: %d\n", len);
  quickSort(arr, 0, len - 1);
}
int main (void) {
  int arr1[] = {1, 9, 3, 4, 79, 27};
  int arr2[] = {2, 56, 35, 7, 19};
  int len1 = sizeof(arr1) / sizeof(*arr1);
  int len2 = sizeof(arr2) / sizeof(*arr2);
  int len = len1 + len2;
  printf("length %d length\n", len);
  int* arr = (int*)malloc(len * sizeof(int));
  mergeArrSorted(arr, len, arr1, len1, arr2, len2);

  for (int k = 0; k < len; k++)
  {
    printf("%d ", arr[k]);
  }
  printf("\n");
}
在把數組傳遞爲函數的實參時,也要顯式的傳遞函數長度。由於函數sizeof在編譯時求值,傳遞給函數的實參所指向的數組長度肯定,可是函數的形參獲取到的數組的長度在編譯時是不肯定的,所以在函數內部使用sizeof獲取數組長度會出現不肯定的值。
所以,在C語言中,傳遞數組的同時,也要顯式傳遞數組的長度。
2、開闢一個新的數組arr,用來存儲這兩個有序數組中的元素。這裏用到歸併排序的merge方法,即將兩個指針指向兩個數組頭部,而後比較指針所指的元素,將較小的元素保存到arr數組,並將其指針後移。重複上面的步驟,直到兩個數組遍歷徹底。這種方法,須要循環m+n次,所以時間複雜度爲O(m+n),可是由於須要開闢額外的空間來存儲這兩個數組的元素,所以空間複雜度爲O(m+n)。
void mergeArrSorted(int* arr, int len, int *arr1, int len1, int *arr2, int len2)
{

  int index1 = 0;
  int index2 = 0;
  for (int k = 0; k < len; k++)
  {
    // 若是是其餘語言,能夠在兩個數組末尾各放一個Infiniti值,
    // 這樣判斷比較到末尾時,恰好循環len次,下面的代碼也可精簡爲
    // if (arr1[index1] <= arr2[index2])
    //  {
    //    arr[k] = arr1[index1];
    //    index1++;
    //  }
    //  else
    //  {
    //    arr[k] = arr2[index2];
    //    index2++;
    //  }
    // 由於這樣就少了數組越界的判斷
    
    
    // 下面的代碼的三個外層判斷,就是爲了防止數組訪問越界
    if (index1 < len1 && index1 < len2)
    {
      if (arr1[index1] <= arr2[index2])
      {
        arr[k] = arr1[index1];
        index1++;
      }
      else
      {
        arr[k] = arr2[index2];
        index2++;
      }
    }
    else if (index1 < len1 && index1 >= len2)
    {
      arr[k] = arr1[index1];
      index1++;
    }
    else if (index1 >= len1 && index1 >= len2)
    {
      arr[k] = arr2[index2];
      index2++;
    }
  }
}

下面是測試代碼:性能

#include <stdio.h>
#include <stdlib.h>


void mergeArrSorted(int* arr, int len, int *arr1, int len1, int *arr2, int len2)
{

  int index1 = 0;
  int index2 = 0;
  for (int k = 0; k < len; k++)
  {
    if (index1 < len1 && index1 < len2)
    {
      if (arr1[index1] <= arr2[index2])
      {
        arr[k] = arr1[index1];
        index1++;
      }
      else
      {
        arr[k] = arr2[index2];
        index2++;
      }
    }
    else if (index1 < len1 && index1 >= len2)
    {
      arr[k] = arr1[index1];
      index1++;
    }
    else if (index1 >= len1 && index1 >= len2)
    {
      arr[k] = arr2[index2];
      index2++;
    }
  }
}

int main (void) {
  int arr1[] = {1, 3, 4, 5, 8};
  int arr2[] = {2, 7, 10, 18, 21};
  int len1 = sizeof(arr1) / sizeof(int);
  int len2 = sizeof(arr2) / sizeof(int);
  int len = len1 + len2;
  int* arr = (int*)malloc(len  * sizeof(int));
  mergeArrSorted(arr, len, arr1, len1, arr2, len2);
  for (int i = 0; i < len; i++) {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
LeetCode原題:

給你兩個有序整數數組 nums1 和 nums2,請你將 nums2 合併到 nums1 中,使 nums1 成爲一個有序數組。
條件說明:測試

  1. 初始化 nums1 和 nums2 的元素數量分別爲 m 和 n 。
  2. 你能夠假設 nums1 有足夠的空間(空間大小大於或等於 m + n)來保存 nums2 中的元素。

讀完題目能夠知道,題目要求將子數組nums2合併到nums1,由於nums1有足夠的空間可以容納兩個數組的全部元素。跟上面合併數組比起來,有空間的要求,就是咱們必須把num2合併到num1,此外區別不大。所以,上面講過的兩種方法均可以實現數組合並。
方法一:
不須要額外的空間保存數組,只需將num2保存到num1後面,而後對nums1進行快速排序便可。所以,時間複雜度不變,仍是O((m+n)lg(m+n)),即快排的時間複雜度。空間複雜度爲O(1),由於不須要開闢額外的空間。
方法二:
由於須要將nums1做爲輸出數組,所以,要先將nums1的元素存儲到一個新的數組,而後使用雙指針法,比較兩個數組中的元素大小,並依次將其保存到nums1。所以須要額外開闢m的空間來存儲nums1中的元素。所以空間複雜度O(m),時間複雜度不變,爲O(m+n)。
方法三:
能夠看到,第一種方法空間複雜度很優秀,可是時間複雜度遜色於第二種方法。咱們須要找出時間複雜度如方法二通常優秀,空間複雜度如方法一通常優秀的算法,那就是雙指針法--從後往前遍歷。
雙指針從後往前遍歷,將較大的值存儲到nums1尾部,如此循環,直到全部元素都有序存儲到nums1中。時間複雜度O(m+n),空間複雜度O(1)。ui

void merge(int *nums1, int nums1Size, int m, int *nums2, int nums2Size, int n)
{
  int len = m + n;
  m = m - 1;
  n = n - 1;
  for (int i = len - 1; i >= 0; i--)
  {
    if (n >= 0 && m >= 0)
    {
      if (nums2[n] > nums1[m])
      {
        nums1[i] = nums2[n];
        n--;
      }
      else
      {
        nums1[i] = nums1[m];
        m--;
      }
    }
    else if (n >= 0 && m < 0)
    {
      nums1[i] = nums2[n];
      n--;
    }
    else if (n < 0 && m >= 0)
    {
      nums1[i] = nums1[m];
      m--;
    }
  }
}
性能分析

下面是測試代碼,有興趣的能夠拿去編譯運行一下:spa

#include <stdio.h>
#include <stdlib.h>

void merge(int *nums1, int nums1Size, int m, int *nums2, int nums2Size, int n)
{
  int len = m + n;
  m = m - 1;
  n = n - 1;
  for (int i = len - 1; i >= 0; i--)
  {
    if (n >= 0 && m >= 0)
    {
      if (nums2[n] > nums1[m])
      {
        nums1[i] = nums2[n];
        n--;
      }
      else
      {
        nums1[i] = nums1[m];
        m--;
      }
    }
    else if (n >= 0 && m < 0)
    {
      nums1[i] = nums2[n];
      n--;
    }
    else if (n < 0 && m >= 0)
    {
      nums1[i] = nums1[m];
      m--;
    }
  }
}

int main (void) {
  int arr1[10] = {1, 2, 3, 8, 20};
  int arr2[] = {6, 9, 10};
  int len1 = sizeof(arr1) / sizeof(*arr1);
  int len2 = sizeof(arr2) / sizeof(*arr2);
  int m = 5;
  int n = len2;
  int len = m + n;
  merge(arr1, len1, m, arr2, len2, n);
  for (int i = 0; i < len; i++)
  {
    printf("%d ", arr1[i]);
  }
  printf("\n");
}
相關文章
相關標籤/搜索