排序算法
堆排序
步骤
1.构建最大堆:从最后一个非叶子结点开始,依次往上调整堆,确保每个结点都符合最大堆的性质。
2.交换元素:将根节点(最大值)与最后一个结点交换,
3.调整堆:剔除数组中已经交换过的结点,将剩余结点重新构建堆。
4.重复2,3两个步骤
整体逻辑
从最后一个非叶子结点开始,构建一个最大堆,然后将堆的顶点移动到末尾,再将数组的前n-1个结点构建成最大堆,重复上述的过程。
细节处理
1.确保for循环能够处理所有的非叶子结点
for(int i = n/2-1; i >=0; --i)
此处容易忽略根节点,i = 0的情况
2.确保所有的叶子结点都能正确匹配
if(left <=n && vec[larget] < vec[left]) if(right <=n && vec[larget] < vec[right])
此处容易忽略=号导致右孩子结点处理不到
代码展示
#include <iostream> #include <vector> using namespace std; class heap { private: vector<int> vec; public: heap(vector<int> _vec) :vec(_vec.begin(), _vec.end()) {} // 生成最大堆 // i 当前数组的最后一个非叶子结点 // n 数组的长度 void heapCreate(int i, int n); // 将堆的顶点移动到末尾,生成递增排序的数组 void sort(int n); friend ostream & operator<<(ostream & os, const heap & rhs); }; // 对每个非叶子结点进行调整,从下往上 void heap::heapCreate(int i, int n) { int larget = i; int left = 2 * i + 1; int right = 2 * i + 2; if(left <=n && vec[larget] < vec[left]) { larget = left; } if(right <=n && vec[larget] < vec[right]) { larget = right; } if(larget != i) { swap(vec[i],vec[larget]); } } void heap::sort(int n) { // 递归出口 if(n == 0) { return; } // 先构建一个大根堆 for(int i = n/2-1; i >=0; --i) { heapCreate(i, n); } // 将调整好的大根堆的第一个结点挪到末尾 swap(vec[0],vec[n]); // 递归 sort(n-1); } ostream & operator<<(ostream & os, const heap & rhs) { for(auto & i: rhs.vec) { os << i << " "; } os << "\n"; return os; } void test() { vector<int> vec = {6,7,8,4,5,3,9}; heap h(vec); h.sort(vec.size()-1); cout << h; } int main() { test(); return 0; }
快排
步骤
1.选择基准:一般以最左边的结点为基准,比基准小的全部挪到右边,直到访问到基准点,将基准点与index位置的元素进行交换
2.切割数组:对基准左边的数组进行递归,对基准右边的数组进行递归;
3.重复上述1,2。每次递归都会确定一个元素的位置,直到数组的left > right,退出递归;
细节
如果选取左边的首元素为基准点就要从右边开始存储大于基准点的元素。
int index = right; for(int i = right; i > left; --i);
代码展示
#include <iostream> #include <vector> using namespace std; class Kuaipai { private: vector<int> vec; public: Kuaipai(vector<int>_vec) :vec(_vec.begin(),_vec.end()) {} void sort(int left, int right); int reference(int left, int right); void print() { for(auto &i : vec) { cout << i <<" "; } cout << "\n"; } friend ostream & operator<<(ostream & os, const Kuaipai & rhs); }; void Kuaipai::sort(int left, int right) { // 递归出口 if(left < right) { // 从0开始 int dump = reference(left, right); // 对左边进行快排 sort(left,dump-1); sort(dump+1,right); } } int Kuaipai::reference(int left,int right) { // 假设第一次选的基准点是左边第一个,从右往左,比他大的都放在右边 // 每一次进入循环都要选取基准点,一般是一段数组的首位,一直遍历到该基准点为止 int index = right; for(int i = right; i > left; --i) { if(vec[i] > vec[left]) { std::swap(vec[i],vec[index--]); } } swap(vec[left],vec[index]); return index; } ostream & operator<<(ostream & os, const Kuaipai & rhs) { for(auto &i : rhs.vec) { os << i <<" "; } os << "\n"; return os; } int main() { vector<int> vec = {6,7,8,4,5,3,9}; Kuaipai k(vec); k.sort(0,vec.size()-1); cout << k; return 0; }
回溯
分割
分割回文串
由上知,当回溯到第一层for循环时,str变成了"aa",所以每次的处理逻辑是截取前i个字符串
s.substr(0,i+1); 或者 string str; for (int j = startNum; j <= i; ++j) { str += s[j]; } if (str.size() != 0) { vec.push_back(str); }
根据模板,递归出口需要修改,结合例子的输出结果,可以看出vector<string>中的每个字符串都要是回文,而且所有字符串加在一起的长度等于输入s的长度
由此得出代码如下
class Solution { private: vector<vector<string>> result; vector<string> vec; public: void dfs(string s, int startNum) { // 递归出口 if (IsVector(vec) && calculate(vec) == s.size()) { result.push_back(vec); } // 横向递归,vec中存储的是所有可能的回文子串 for (int i = startNum; i < s.size(); ++i) { // 处理逻辑,截断字符串 string str; for (int j = startNum; j <= i; ++j) { str += s[j]; } if (str.size() != 0) { vec.push_back(str); } // 纵向递归,增加递归的深度 dfs(s, i+1); // 回溯 vec.pop_back(); } } // 对vector数组的每个字符串进行判断 bool IsVector(vector<string> s) { if(vec.size() == 0) return false; for (auto& i : s) { if (!IsPalindrome(i)) { return false; } } return true; } // 判断一个字符串是不是回文串 bool IsPalindrome(string s) { if(s.size() == 0) return false; // 用deque deque<char> deq; for (auto& i : s) { deq.push_back(i); } for (auto& j : s) { auto c = deq.back(); deq.pop_back(); if (c != j) { return false; } } return true; } // 计算数组中字符串的长度 int calculate(vector<string> vec){ int len = 0; for(auto & i : vec){ len += i.size(); } return len; } vector<vector<string>> partition(string s) { dfs(s, 0); return result; } };
复原IP地址
细节1
stoi('.')会报错,所以需要在else逻辑下对num进行判断,如下
for (auto& i : path) { if (i != '.') { str += i; } else{ // 首字母不能为0 if(str.size() > 1){ if(str[0] == '0') return false; } num = stoi(str); if (num > 255) return false; str.clear(); } }
这样会导致最后一个'.'后的字符串没有进行判断所以在判断一个字符串之前首先判断最后一个字符串
// 先判断最后一个.之后的数字 // 找到最后一个.的位置 size_t pos = path.rfind('.'); string temp = path.substr(pos + 1); if(temp.size() > 1){ if(temp[0] == '0') return false; } if(stoi(path.substr(pos + 1)) > 255) return false;
细节2
回溯的时候,times也要进行回溯,如果times == 4不回溯,会重复进入递归出口,然后退出,继续回溯。times还是==4还会进入递归出口,退出,直到退出到第一层for循环,times还是会==4;
代码
class Solution { private: vector<string> result; string path; public: void dfs(string s, int startNum, int times); bool IsIp(); vector<string> restoreIpAddresses(string s) { dfs(s, 0, 0); return result; } }; bool Solution::IsIp() { // 先判断最后一个.之后的数字 // 找到最后一个.的位置 size_t pos = path.rfind('.'); string temp = path.substr(pos + 1); if(temp.size() > 1){ if(temp[0] == '0') return false; } if(stoi(path.substr(pos + 1)) > 255) return false; string str; int num = 0; for (auto& i : path) { if (i != '.') { str += i; } else{ // 首字母不能为0 if(str.size() > 1){ if(str[0] == '0') return false; } num = stoi(str); if (num > 255) return false; str.clear(); } } return true; } // 需要拼接字符串 void Solution::dfs(string s, int startNum, int times) { // 递归出口 if (times == 4) { if (path.size() == (s.size() + 3) && IsIp()) { result.push_back(path); return; } return; } // 横向递归 for (int i = startNum; i <= startNum + 3; ++i) { // 进行4次纵向递归 string old = path; string str; for (int j = startNum; j <= i; ++j) { str += s[j]; } if (times < 3) { str += "."; } times++; path += str; // 纵向递归 dfs(s, i + 1, times); // 回溯的时候times也要-- path = old; times--; } }