数据结构与算法笔记一 - 复杂度与排序算法
– 根据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)=a∗T(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,时间复杂度为Nd∗logba
-
归并排序的 时间复杂度为
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]); } }
-
排序总结:
- 优先选择快速排序算法,因为其常数操作时间最短
- 优先选择快速排序算法,因为其常数操作时间最短
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);
-