数据结构和算法学习

tech2022-10-21  104

数据结构

数据结构是计算机内部数据的组织形式和存储方法,常用的数据结构有线性结构、数结构、图结构。

线性结构

线性结构主要包括:顺序表、链表、栈、队列等基本形式。其中顺序表和链表是从存储形式上(或物理结构上)区分的,而栈和队列是从逻辑功能上区分的。也就是说,顺序表和链表是线性数据结构的基础,队列和栈是基于顺序表和链表的,它们由顺序表或链表构成。

栈的实现

#ifndef STACK_HPP#define STACK_HPP#include <iostream>using namespace std;template <class T>class Stack {public: Stack(); Stack(int capacity); ~Stack(); bool IsEmpty(); // 判断是否为空 bool IsFull(); // 判断栈是否已满 void ClearStack(); // 清空栈 int Length(); // 获取栈的长度 bool Push(const T &element); // 获取元素 bool Pop(T &ele); // 弹出栈顶元素 void StackTraverse(); // 遍历栈private: T *m_stack; // 容器 int m_top; // 栈顶 int m_capacity; // 容器容量};template <typename T>Stack<T>::Stack() { cout << "构造函数" << endl;}template <typename T>Stack<T>::Stack(int capacity) { this->m_capacity = capacity; this->m_top = 0; this->m_stack = new T[this->m_capacity];}template <typename T>Stack<T>::~Stack() { if (this->m_stack != NULL) { delete[] this->m_stack; this->m_stack = NULL; }}template <typename T>bool Stack<T>::IsEmpty() { return this->m_top == 0 ? true : false;}template <typename T>bool Stack<T>::IsFull() { return this->m_top == this->m_capacity ? true : false;}template <typename T>void Stack<T>::ClearStack() { this->m_top = 0;}template <typename T>int Stack<T>::Length() { return this->m_top;}template <typename T>bool Stack<T>::Push(const T &element) { if (IsFull()) { return false; } else { this->m_stack[this->m_top++] = element; return true; }}template <typename T>bool Stack<T>::Pop(T &ele) { if (!IsEmpty()) { this->m_top--; ele = this->m_stack[this->m_top]; return true; } else { return false; }}template <typename T>void Stack<T>::StackTraverse() { if (!IsEmpty()) { for (int i = m_top - 1; i >= 0; --i) { cout << this->m_stack[i] << ' '; } cout << endl; } else { cout << "栈为空" << endl; }}#endif

队列的实现

#ifndef MYQUEUE_HPP#define MYQUEUE_HPP#include <iostream>using namespace std;template <typename T>struct QueueNode { T data; QueueNode *next; QueueNode () { this->next = NULL; }};template <class T>class MyQueue {public: MyQueue() { this->count = 0; this->head = new QueueNode <T>(); this->tail = new QueueNode <T>(); this->tail = this->head; } ~MyQueue() { if (this->head != NULL) { cout << "释放head" << endl; delete[] this->head; this->head = NULL; } if (this->tail != NULL) { cout << "释放tail" << endl; delete[] this->tail; this->tail = NULL; } } int size() { return this->count; } bool IsEmpty() { if (size() == 0) { return true; } else { return false; } } void InQueue(T val) { QueueNode <T> *temp = new QueueNode<T>(); temp->data = val; this->tail->next = temp; this->tail = temp; this->count++; } QueueNode<T>* OutQueue() { if (this->count == 0) { cout << "队列为空" << endl; return NULL; } QueueNode <T> *temp = this->head->next; if (temp) { this->head->next = temp->next; temp->next = NULL; this->count--; } return temp; } void QueueTraverse() { if (this->count == 0) { cout << "队列为空" << endl; return; } QueueNode <T> *temp = head->next; while (temp) { cout << temp->data << ' '; temp = temp->next; } cout << endl; } void Remove() { this->head->next = NULL; this->tail = this->head; this->count = 0; }private: int count; QueueNode <T> *head; QueueNode <T> *tail;};#endif

链表反转

使用两个节点pre和next,先用next保存当前节点的next指针,然后将当前节点next指针指向上一个节点pre,接下来将当前节点赋值给pre,next赋值给当前节点进行下一循环,直到当前节点为NULL。

struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) {}};/** * 链表反转 */ListNode* ReverseList(ListNode *pHead) { if(pHead == NULL) return NULL; ListNode *pre = NULL; ListNode *next = NULL; while (pHead != NULL) { next = pHead->next; pHead->next = pre; pre = pHead; pHead = next; } return pre;}

寻找第k大

int Partition(vector<int> &arr, int start, int end) { // 降序排列 if (start == end) return start; int left = start, right = end, key = arr[start]; while (left < right) { while (left < right && arr[right] <= key) right--; arr[left] = arr[right]; while (left < right && arr[left] >= key) left++; arr[right] = arr[left]; } arr[left] = key; return left;}/** * 寻找第k大 */int findKth(vector<int> a, int n, int K) { int low = 0; int high = n - 1; int mid; while (low <= high) { mid = Partition(a, low, high); if (K < mid + 1) { // 在左半段 high = mid - 1; } else if (K > mid + 1) { // 在右半段 low = mid + 1; } else { return a[mid]; } } return -1;}

括号序列

给出一个仅包含字符’(‘,’)’,’{‘,’}’,’[‘和’]’,的字符串,判断给出的字符串是否是合法的括号序列,括号必须以正确的顺序关闭,”()”和”()[]{}”都是合法的括号序列,但”(]”和”([)]”不合法。

bool isValid(string s) { // 如果字符串长度不是偶数,则不合法 if (s.size() & 1 != 0) return false; stack<char> temp; for (int i = 0; i < s.size(); ++i) { if (s[i] == '(' || s[i] == '{' || s[i] == '[') temp.push(s[i]); else { if (temp.empty()) return false; switch (s[i]) { case '}': if (temp.top() == '{') temp.pop(); break; case ']': if (temp.top() == '[') temp.pop(); break; case ')': if (temp.top() == '(') temp.pop(); break; default: return false; } } } if (!temp.empty()) return false; return true;}

链表中的节点每k有一组翻转

题目描述

将给出的链表中的节点每$k$个一组翻转,返回翻转后的链表如果链表中的节点数不是$k$的倍数,将最后剩下的节点保持原样你不能更改节点中的值,只能更改节点本身。要求空间复杂度$O(1)$。例如:给定的链表是$1→2→3→4→5$

对于 $k = 2$, 你应该返回 $2→1→4→3→5$对于 $k = 3$, 你应该返回 $3→2→1→4→5$ /** * 链表中的节点每k有一组翻转 * @param head * @param k * @return */ListNode* reverseKGroup(ListNode* head, int k) { if (head == NULL) return NULL; ListNode *temp = head; int len = 0; while (temp != NULL) { temp = temp->next; ++len; } temp = head; if (len < k) return head; ListNode *pre = NULL; ListNode *next = NULL; for (int i = 0; i < k; ++i) { next = head->next; head->next = pre; pre = head; head = next; } temp->next = reverseKGroup(head, k); return pre; }

判断链表中是否有环

链表和快慢指针

/** * 判断给定的链表是否有环 * @param head * @return */bool hasCycle(ListNode *head) { ListNode *low, *quick; low = quick = head; while (quick->next->next != NULL && quick->next != NULL) { low = low->next; quick = quick->next->next; if (low == quick) return true; } return false;}

链表中环的入口

ListNode *detectCycle(ListNode *head) { if (head == NULL) return 0; ListNode *slow = head; ListNode *fast = head; while (fast != NULL && fast->next != NULL) { slow = slow->next; fast = fast->next->next; if (slow == fast) break; } if (fast == NULL || fast->next == NULL) return NULL; fast = head; while (slow != fast) { slow = slow->next; fast = fast->next; } return slow;}

合并有序链表

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { ListNode *head = new ListNode(-1); ListNode *temp = head; while (l1 != NULL || l2 != NULL) { if (l1 == NULL) { temp->next = l2; break; } if (l2 == NULL) { temp->next = l1; break; } if (l1->val < l2->val) { temp->next = l1; l1 = l1->next; } else { temp->next = l2; l2 = l2->next; } temp = temp->next; } return head;}

删除链表的倒数第N个节点

/** * 删除链表倒数第N个节点 * @param head ListNode类 * @param n int整型 * @return ListNode类 */ListNode* removeNthFromEnd(ListNode* head, int n) { if (head == NULL) return NULL; ListNode *dummy = new ListNode(0); dummy->next = head; head = dummy; ListNode *slow = head; ListNode *fast = head; for (int i = 0; i < n; ++i) { fast = fast->next; } while (fast->next != NULL) { slow = slow->next; fast = fast->next; } ListNode *temp = slow->next; slow->next = slow->next->next; delete temp; return dummy->next;}

两个链表相加生成相加链表

/** * 两个链表相加生成相加链表 */ListNode* addInList(ListNode* head1, ListNode* head2) { stack<ListNode *> s1; stack<ListNode *> s2; while (head1) { s1.push(head1); head1 = head1->next; } while (head2) { s2.push(head2); head2 = head2->next; } int flag = 0; while (!s1.empty() || !s2.empty()) { int sum = flag; if (!s1.empty()) { sum += s1.top()->val; head1 = s1.top(); s1.pop(); } if (!s2.empty()) { sum += s2.top()->val; if (s2.size() > s1.size()) head1 = s2.top(); s2.pop(); } if (sum < 10) { head1->val = sum; flag = 0; } else { head1->val = sum % 10; flag = sum / 10; } } if (flag > 0) { ListNode *head = new ListNode(flag); head->next = head1; return head; } return head1;}

树结构

数据之间存在“一对多”的关系构成了树结构。

图结构

图结构中数据元素之间存在着“多对多”的关系。

算法

查找和排序

顺序查找

int search(Student *stu, int n, int key) { for (int i = 0; i < n; ++i) { if (stu[i].id == key) return i; } return -1;}

折半查找

/** * 折半查找 * @param key 关键字顺序表 * @param n 记录个数 * @param k 要查找的关键字 * @return */int bin_search(int *key, int n, int k) { int low = 0, high = n - 1, mid; while (low <= high) { mid = (low + high) / 2; if (key[mid] == k) return mid; if (k > key[mid]) low = mid + 1; else high = mid - 1; } return -1;}

请实现有重复数字的有序数组的二分查找。输出在数组中第一个大于等于查找值的位置,如果数组中不存在这样的数,则输出数组长度加一。

int bin_search(int n, int v, vector<int>& a) { int left = 0; int right = n - 1; int mid; while (left <= right) { mid = (left + right) / 2; if (a[mid] >= v) { if (mid == 0 || a[mid-1] < v) return mid + 1; else right = mid - 1; } else left = mid + 1; } return n + 1;}

直接插入排序

/** * 直接插入排序 * @param arr 数组 * @param n 数组元素个数 */void insertSort(int arr[], int n) { for (int i = 1; i < n; ++i) { int temp = arr[i]; int j = i - 1; // for (j; j >= 0; --j) { // if (temp < arr[j]) { // arr[j + 1] = arr[j]; // } else { // break; // } // } // arr[j + 1] = temp; while (j >= 0 && temp < arr[j]) { arr[j+1] = arr[j]; j--; } arr[j+1] = temp; }}

选择排序

/** * 选择排序 * @param arr 数组 * @param n 数组元素个数 */void selectSort(int arr[], int n) { for (int i = 0; i < n; ++i) { int min = i, temp; for (int j = i; j < n; ++j) { if (arr[j] < arr[min]) min = j; } if (min != i) { temp = arr[min]; arr[min] = arr[i]; arr[i] = temp; } }}

冒泡排序

/** * 冒泡排序 * 添加标志变量flag,进行交换flag为真,可以进入下一循环,为进行交换flag为假(数据已有序,无须再进行交换)循环终止。 * @param arr 数组 * @param n 数组元素个数 */void BubbleSort(int arr[], int n) { bool flag = true; for (int i = 0; i < n && flag; ++i) { flag = false; for (int j = 0; j < n - i - 1; ++j) { if (arr[j] > arr[j + 1]) { flag = true; int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } }}

希尔排序

/** * 希尔排序 * 使用do-while是因为flag为2时已经有序,但是flag为1时不是有序的。 * @param arr 数组 * @param n 数组元素个数 */void shellSort(int arr[], int n) { int i, j, gap = n; bool flag = true; int temp; while(gap > 1) { gap = gap / 2; do { flag = false; for (i = 0; i < n - gap; ++i) { j = i + gap; if (arr[i] > arr[j]) { temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; flag = true; } } } while(flag); }}

快速排序

void swap(int &a, int &b) { int temp = a; a = b; b = temp;}void quick(int arr[], int start, int end) { if (start >= end) return; int left = start, right = end, key = arr[start]; // 进行循环,将小于基准元素的元素放到左边,大于基准元素的元素放到右边 while (left < right) { while(left < right && arr[right] >= key) right--; while(left < right && arr[left] <= key) left++; swap(arr[left], arr[right]); // for (int i = 0; i < 10; ++i) { // cout << arr[i] << ' '; // } // cout << endl; } // 交换基准元素与left的位置,循环中先从右开始,因此left和right相等的位置为小于基准元素的值 swap(arr[start], arr[left]); quick(arr, start, left - 1); quick(arr, left + 1, end);}/** * 快速排序 * @param arr 数组 * @param n 数组元素个数 */void quickSort(int arr[], int n) { quick(arr, 0, n - 1);}

循环实现

int Partition(int *arr, int start, int end) { if (start == end) return start; int left = start, right = end, key = arr[start]; while (left < right) { while (left < right && arr[right] >= key) right--; arr[left] = arr[right]; while (left < right && arr[left] <= key) left++; arr[right] = arr[left]; } arr[left] = key; return left;}/** * 非递归实现快排 */void quickSort2(int arr[], int n) { stack<int> st; st.push(0); st.push(n-1); while (!st.empty()) { int right = st.top(); st.pop(); int left = st.top(); st.pop(); int mid = Partition(arr, left, right); if (mid - 1 > left) { st.push(left); st.push(mid - 1); } if (mid + 1 < right) { st.push(mid + 1); st.push(right); } }}

堆排序

/** * 调整大顶堆 * @param arr * @param i * @param len */void adjust(int arr[], int i, int len) { int temp = arr[i]; // 从i结点的左子结点开始,也就是2i+1 for (int k = 2 * i + 1; k < len; k = k * 2 + 1) { // 如果左子结点小于右子结点,k指向右子结点 if (k + 1 < len && arr[k] < arr[k + 1]) k++; // 如果子结点大于父节点,将子结点的值赋给父节点 if (arr[k] > temp) { arr[i] = arr[k]; i = k; } else { break; } } arr[i] = temp;}/** * 堆排序 * @param arr 数组 * @param n 数组元素个数 */void headSort(int arr[], int n) { int i, temp; // 1.构建大顶堆 for (i = n / 2; i >= 0; --i) { // 从第一个非叶子结点从下至上,从右至左调整结构 adjust(arr, i, n); } // 2.调整堆结构并交换堆顶元素与末尾元素 for (int j = n - 1; j > 0; --j) { swap(arr[0], arr[j]); adjust(arr, 0, j); }}

回溯

四皇后问题

在一个4×4的国际象棋棋盘上,一次一个地摆放4枚皇后棋子,摆好后满足每行、每列和对角线上只允许出现一枚棋子,即棋子间不许相互俘获。

解决策略:

初始化一个4x4的数组Q,Q[i][j]=1表示该点有皇后,无皇后的点置为0。遍历每一行,使用isCorrect判断当前点是否可以放置皇后,可以放置后使用递归将皇后数量+1确定四个皇后之后将上个确定的皇后置为0进行回溯,继续执行循环。 // 判断皇后是否可以放置到[i][j]int isCorrect(int i, int j, int (*QueenArray)[4]) { int s, t; // 判断行 for (s = i, t = 0; t < 4; ++t) if (QueenArray[s][t] == 1 && t != j) return 0; // 判断列 for (s = 0, t = j; s < 4; ++s) if (QueenArray[s][t] == 1 && s != i) return 0; // 判断左上 for (s = i - 1, t = j - 1; s >= 0 && t >= 0; s--, t--) if (QueenArray[s][t] == 1) return 0; // 判读右上 for (s = i - 1, t = j + 1; s >= 0 && t < 4; s--, t++) if (QueenArray[s][t] == 1) return 0; // 判断左下 for (s = i + 1, t = j - 1; s < 4 && t >= 0; s++, t--) if (QueenArray[s][t] == 1) return 0; // 判读右下 for (s = i + 1, t = j + 1; s < 4 && t < 4; s++, t++) if (QueenArray[s][t] == 1) return 0; return 1;} /** * 回溯法解决4皇后问题 * @param queenNum 当前皇后数量 * @param QueenArray 皇后数组 */void Queen(int queenNum, int (*QueenArray)[4]) { int i, k; // 四个皇后都确定 if (queenNum == 4) { for (i = 0; i < 4; i++) { for (k = 0; k < 4; k++) cout << QueenArray[i][k] << ' '; cout << endl; } cout << endl; count++; return; } for (i = 0; i < 4; i++) { if (isCorrect(i, queenNum, QueenArray)) { QueenArray[i][queenNum] = 1; Queen(queenNum+1, QueenArray); QueenArray[i][queenNum] = 0; } }}

动态规划

上台阶问题

有一楼梯共m级,若每次只能跨上一级或二级,要走上第m级,共有多少走法

/** * 上台阶问题 * @param n 台阶数 * @return */int UpStairs(int n) { if (n < 0) return -1; if (n == 1) return 1; if (n == 2) return 2; return UpStairs(n - 1) + UpStairs(n - 2);} // 使用动态规划解决int UpStairs(int n) { if (n < 0) return -1; if (n == 1) return 1; if (n == 2) return 2; int *dp = new int[n+1]; dp[0] = 0; dp[1] = 1; dp[2] = 2; for (int i = 3; i <= n; ++i) dp[i] = dp[i-1] + dp[i-2] return dp[n];}

编辑距离

已知两个字符串word1和word2,求从word1转化成word2最少需要几步。其中,每一步只能进行以下三个操作之一:

插入一个字符删除一个字符替换一个字符

解决策略:

使用一个二维数组dp记录需要的操作数dp[i][j]表示word1转化为word2需要的最小操作数,有两种状态,word1[i]==word2[j]和word1[i]!=word2[j], word1[i] == word2[j],则dp[i][j] = dp[i-1][j-1],word1[i] !=wrod2[j],有三条路径: 比如,”xyz” => “efg” 的最小编辑距离等于 “xy” => “efg” 的最小编辑距离 + 1(因为允许插入操作,插入一个 “z”),抽象的描述便是 d[m][n] === d[m-1][n] + 1。比如,”xyz” => “efg” 的最小编辑距离等于 “xyzg” => “efg” 的最小编辑距离 + 1,且因为最后一个字符都是 “g”,根据第一个判断条件,可以再等于 “xyz” => “ef” 的最小编辑距离 + 1,因此,得到结论:”xyz” => “efg” 的最小编辑距离等于 “xyz” => “ef” 的最小编辑距离 + 1,抽象的描述便是:d[m][n] === d[m][n-1] + 1。比如,”xyz” => “efg” 的最小编辑距离等于 “xyg” => “efg” 的最小编辑距离 + 1(因为允许替换操作,可以把 “g” 换成 “z”),再等于 “xy” => “ef” 的编辑距离 + 1(根据第一个判断条件),抽象的描述便是: d[m][n] === d[m-1][n-1] + 1。上述三种情况都有可能出现,因此,取其中的最小值便是整体上的最小编辑距离。 /** * 编辑距离 */int miniDistance(string word1, string word2) { int n1 = word1.size(); int n2 = word2.size(); // vector <vector <int>> dp[n1][n2]; // 开辟空间 int **dp = new int*[n1+1]; for (int i = 0; i <= n1; ++i) dp[i] = new int[n2+1](); // 初始化 for (int i = 0; i < n1; ++i) dp[i+1][0] = dp[i][0] + 1; for (int i = 0; i < n2; ++i) dp[0][i+1] = dp[0][i] + 1; for (int i = 1; i <= n1; ++i) { for (int j = 1; j <= n2; ++j) if (word1[i-1] == word2[j-1]) dp[i][j] = dp[i-1][j-1]; else dp[i][j] = min(min(dp[i-1][j-1], dp[i-1][j]), dp[i][j-1]) + 1; } // 释放空间 for (int i = 0; i < n1; ++i) delete[] dp[i]; delete[] dp; return dp[n1][n2];}

贪心

最新回复(0)