数据结构与算法笔记一 - 复杂度与简单排序算法

本文详细介绍了几种基本排序算法,包括选择排序、冒泡排序、插入排序和归并排序,以及快速排序和堆排序。讨论了它们的时间复杂度,特别提到了归并排序的时间复杂度为O(N*logN)。此外,还涉及二分查找法、随机数生成及异或操作的应用。最后,通过测试案例验证了排序算法的正确性。
摘要由CSDN通过智能技术生成

数据结构与算法笔记一 - 复杂度与排序算法

– 根据B站左程云大佬的课程制作的笔记


1. 时间复杂度

  • 常数操作:与样本数据量无关,每次都是固定时间内完成的操作
  • 时间复杂度:以0(big 0)表示,常数操作的次数表达式中的最高阶
  • 算法时间复杂度相同是,需要分析算法在不同数据样本下的实际运行时间,即,常数项时间

2. 排序算法

  • 选择排序:

    void selectionSort(int arr[], int length) {
        int minIndex;
        int temp;
    
        for (int i = 0; i < length - 1; ++i) {
            minIndex = i;
            for (int j = i + 1; j < length; ++j) {
                if (arr[j] < arr[minIndex]) minIndex = j;
            }
    
            temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
    
  • 冒泡排序:

    void bubbleSort(int arr[], int length) {
        for (int i = length - 1; i != 0; --i) {
            for (int j = 0; j != i; ++j) {
                if (arr[j] > arr[j + 1]) swapInt(arr[j], arr[j + 1]);
            }
        }
    }
    
  • 插入排序:

    void insertSort(int arr[], int length) {
        if (arr == nullptr || length < 2) return;
        for (int i = 0; i != length; ++i) {
            for (int j = i - 1; j > 0 && arr[j + 1] < arr[j]; --j) swapInt(arr[j + 1], arr[j]);
        }
    }
    
  • 选择排序、冒泡排序、插入排序的时间复杂度均为0(N^2),额外空间复杂度为0(1)

  • 归并排序:

    void mergeSortRecursion(int arr[], int left, int right) {
        if (left == right) return;
        int mid = left + ((right - left) >> 1);
        mergeSortRecursion(arr, left, mid);
        mergeSortRecursion(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }
    
    void merge(int arr[], int left, int mid, int right) {
        int * help = new int[right - left + 1];
        int i = 0;
        int p1 = left, p2 = mid + 1;
        while(p1 <= mid && p2 <= right) {
            help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        while(p1 <= mid) help[i++] = arr[p1++];
        while(p2 <= right) help[i++] = arr[p2++];
        for (int i = 0; i != right - left + 1; ++i) arr[left + i] = help[i];
    }
    
    
    // 归并排序的衍生问题:小和问题
    int mergeSmallSumRecursion(int arr[], int left, int right) {
        if (left == right) return 0;
        int mid = left + ((right - left) >> 1);
        return mergeSmallSumRecursion(arr, left, mid) +
               mergeSmallSumRecursion(arr, mid + 1, right) +
               mergeSmallSum(arr, left, mid, right);
    }
    
    
    int mergeSmallSum(int arr[], int left, int mid, int right) {
        int * help = new int[right - left + 1];
        int p1 = left, p2 = mid + 1;
        int i = 0, res = 0;
        while (p1 <= mid && p2 <= right) {
            res += arr[p1] < arr[p2] ? arr[p1] * (right - p2 + 1) : 0;
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while(p1 <= mid) help[i++] = arr[p1++];
        while(p2 <= right) help[i++] = arr[p2++];
        for (int j = 0; j != right - left + 1; ++j) arr[left + j] = help[j];
        return res;
    }
    
    • master 公式:
      T ( n ) = a ∗ T ( n / b ) + O ( N d ) T(n) = a * T(n/b) + O(N^d) T(n)=aT(n/b)+O(Nd)

      l o g b a < d , 时 间 复 杂 度 为 0 ( N a ) log_ba < d,时间复杂度为0(N^a) logba<d,0(Na)

      l o g b a > d , 时 间 复 杂 度 为 0 ( N l o g b a ) log_ba > d,时间复杂度为0(Nlog_ba) logba>d,0(Nlogba)

      l o g b a = = d , 时 间 复 杂 度 为 N d ∗ l o g b a log_ba == d,时间复杂度为N^d*log_ba logba==d,Ndlogba

    • 归并排序的 时间复杂度为0(N*logN),额外空间复杂度为0(N)

  • 快速排序

    int * partition(int arr[], int left, int right) {
        // left 代表 == 区最左侧下标, right 代表 == 区最右侧下标
        int less = left - 1, more = right;
        // 当前值下标移动到 > 区时停止
        while(left < more) {
            // right 表示划分值下标, left 表示当前值下标
            // 当前值小于划分值时,交换当前值和 > 区左一位值,> 区左扩一位
            // left 不能 +1, 因为交换过来的新值还需要判断
            if (arr[left] > arr[right]) {
                swapInt(arr[left], arr[--more]);
            }
                // 当前值大于划分值时,交换当前值和 < 区后一位值,left + 1
            else if (arr[left] < arr[right]) {
                swapInt(arr[++less], arr[left++]);
            } else {
                left++;
            }
        }
        swapInt(arr[more], arr[right]);
        int * arr1 = new int[2]{less + 1, more};
        return arr1;
    }
    
    
    void quickSortRecursion(int arr[], int left, int right) {
        if (left < right) {
            // 随机选取[left, right]区间中的一个数 与 最有侧数交换,将其作为划分值
            swapInt(arr[left + rand() % (right - left + 1)], arr[right]);
            int * p = partition(arr, left, right);
            quickSortRecursion(arr, left, p[0] - 1);       // < 区
            quickSortRecursion(arr, p[1] + 1, right);       // > 区
            delete [] p;
        }
    }
    
  • 堆排序

    void heapInsert(int arr[], int index) {
        while (arr[index] > arr[(index - 1) / 2]) {
            swapInt(arr[index], arr[(index - 1) / 2]);
            index = (index - 1) / 2;
        }
    }
    
    
    void heapify(int arr[], int index, int heapSize) {
        int left = index * 2 + 1;   // 左孩子下标
    
        while (left < heapSize) {
            // 比较左右两个孩子的大小
            int largest = left;
            if ((left + 1 < heapSize) && (arr[left] < arr[left + 1])) {
                largest = left + 1;
            }
            // 比较父和孩子的大小
            largest = arr[largest] > arr[index] ? largest : index;
    
            if (largest == index) break;
    
            swapInt(arr[largest], arr[index]);
    
            index = largest;
            left = index * 2 + 1;
        }
    }
    
    
    void heapSort(MyArray * arr) {
        if (arr == nullptr || arr->length < 2) return;
    
        // 逐个建立大根堆
        for (int i = 0; i != arr->length; ++i) {
            heapInsert(arr->arr, i);
        }
    
        // 将大根堆中的最大值逐个取出并将最后一个数放到树根处,剩余的树heapify
        int heapSize = arr->length;
        swapInt(arr->arr[0], arr->arr[--heapSize]);
    
        while (heapSize > 0) {
            heapify(arr->arr, 0, heapSize);
            swapInt(arr->arr[0], arr->arr[--heapSize]);
        }
    }
    
  • 排序总结:

    • 优先选择快速排序算法,因为其常数操作时间最短
      来自B站左程云大佬的课程

3. 二分查找法

  • 有序数组中找某个数

    int binarySearchRecursion(int arr[], int left, int right) {
        if (left == right) return arr[left];
        // ((right - left) >> 1) 相当于 (right - left) / 2
        // 不写成 (left + right) / 2 是因为当数组长度过大时可能越界
        int mid = left + ((right - left) >> 1);
        int leftMax = binarySearchRecursion(arr, left, mid);
        int rightMax = binarySearchRecursion(arr, mid + 1, right);
        return max(leftMax, rightMax);
    }
    
  • 正序数组中找出 >= 某数的最左侧位置

  • 局部最小问题

4. 对数器

  • 用于测试编写的算法是否正确

    MyArray * generateRandomArray(int maxValue, int minValue, int maxSize) {
    
        int length = (int)(maxSize * (double)rand() / RAND_MAX);
        cout << "length: " << length << endl;
    
        auto arr = new int[length];
    
        for (int i = 0; i != length; ++i) {
            arr[i] = minValue + (int)(rand() % (maxValue - minValue - 1));
        }
    
        auto * arrRandom = new MyArray{arr, length};
    
        return arrRandom;
    }
    
    
    bool myCompare(MyArray * arr1, MyArray * arr2) {
        bool isEqual = true;
        int length = arr1->length;
        int i = 0;
        while(isEqual && i != length) {
            if (arr1->arr[i] != arr2->arr[i]) {
                isEqual = false;
            }
            ++i;
        }
        return isEqual;
    }
    
    
    int main() {
        srand((unsigned int)time(nullptr));
        int maxValue = 100;
        int minValue = 21;
        int maxSize = 20;
        int testTimes = 100;
        bool success = true;
        MyArray * arrRandom = generateRandomArray(maxValue, minValue, maxSize);
    	MyArray * arrRandomCopy = arrRandom;
    
        for (int i = 0; i != testTimes; ++i) {
            selectionSort(arrRandom);
            bubbleSort(arrRandomCopy->arr, arrRandomCopy->length);
            if (!myCompare(arrRandom, arrRandomCopy)) {
                success = false;
                break;
            }
        }
        
        if (success == true) cout << "Run successfully!" << endl;
        else cout << "Damn it, wrong!" << endl;
    
        delete [] arrRandom;
    
        return 0;
    }
    
  • C++中生成随机数

    #include <cstdlib>
    #include <time.h>
    
    // 生成[0. RAND_MAX] 区间内的随机数
    srand((unsigned)time(NULL))
    
    // 生成某一范围内的随机数
    srand((unsigned)time(NULL))		
    rand() % a							// [0, a)
    rand() % a + b						// [b, a + b -1]
    a + (int)b * rand() / RAND_MAX		// [a, b] 整数
    a + b * rand() / doouble(RAND_MAX)	// [a, b] 浮点数
    

5. 异或 ^

  • 概念:不进位相加,满足交换律和结合律

    a ^ b = b ^ a;
    (a ^ b) ^ c = a ^ (b ^ c);
    0 ^ a = a;
    a ^ a = 0;
    
  • 一些操作

    • 交换a b

      a = a ^ b;
      b = a ^ b;
      a = a ^ b;
      
    • 求一个二进制数最右非零位数:该数与自身取反加一后求与

      eor = a & (~a + 1);
      
    a ^ b = b ^ a;
    (a ^ b) ^ c = a ^ (b ^ c);
    0 ^ a = a;
    a ^ a = 0;
    
  • 一些操作

    • 交换a b

      a = a ^ b;
      b = a ^ b;
      a = a ^ b;
      
    • 求一个二进制数最右非零位数:该数与自身取反加一后求与

      eor = a & (~a + 1);
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值