复杂度
时间复杂度
O(1) - 常数时间复杂度:
- 表示算法的执行时间是固定的,与输入规模无关。
cout << "1*1=1 1*2=2 1*3=3 1*4=4 1*5=5 1*6=6 1*7=7 1*8=8 1*9=9\n" << "2*1=2 2*2=4 2*3=6 2*4=8 2*5=10 2*6=12 2*7=14 2*8=16 2*9=18\n" << "3*1=3 3*2=6 3*3=9 3*4=12 3*5=15 3*6=18 3*7=21 3*8=24 3*9=27\n" << "4*1=4 4*2=8 4*3=12 4*4=16 4*5=20 4*6=24 4*7=28 4*8=32 4*9=36\n" << "5*1=5 5*2=10 5*3=15 5*4=20 5*5=25 5*6=30 5*7=35 5*8=40 5*9=45\n" << "6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36 6*7=42 6*8=48 6*9=54\n" << "7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49 7*8=56 7*9=63\n" << "8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64 8*9=72\n" << "9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81\n";
O(log n) - 对数时间复杂度:
- 通常出现在使用二分法算法中(二分查找)。
O(n) - 线性时间复杂度:
- 表示算法的执行时间与输入规模成线性关系,即随着输入规模的增加而线性增长。
O(n log n) - 线性对数时间复杂度:
- 通常出现在一些高效的排序算法,如快速排序和归并排序。
O(n^x) - 方时间复杂度(一般和嵌套循环个数n有关):
- 表示算法的执行时间与输入规模的平方成正比,通常出现在嵌套循环的算法中。
O(2^n) - 指数时间复杂度:
- 表示算法的执行时间随着输入规模呈指数增长,通常出现在递归算法中。
线性表、栈与队列性质与操作
线性表
类似数组
栈(先进后出)
push入栈、pop出栈、top栈顶、size大小
队列(先进先出)
push入队、pop出队、top队头、size大小
String算法部分(KMP)
重点:next数组计算方法
第0位一定是0,第一位一定是1,之后在1的基础上加最大公共前缀长度(还有-1开头情况,主要看要求)
void myString::GetNext(string p, int next[]) { int i = 0, j = -1; next[0] = -1; while (i < p.length() - 1) { if (j == -1 || p[i] == p[j]) { i++; j++; next[i] = j; } else { j = next[j]; } } for (int i = 0; i < p.length(); i++) { cout << next[i] << " "; }cout << endl; }
获得next数组后使用kmp算法
int myString::KMPFind(string p, int pos, int next[]) { int i = 0, j = 0; while (i < size || j < p.length()) { if (j == -1 || mainstr[i] == p[j]) { i++; j++; } else { j = next[j]; } if (j == p.length()) { return i - j + 1; } } return 0; } void myString::KMPFindSubstr(string p, int pos) { int* next = new int[p.length()]; GetNext(p, next); int v = KMPFind(p, pos, next); delete[] next; cout<< v<<endl; }
树
赫夫曼树
取最小的两个节点相加变成新的节点,直到所有节点都被加完
编码方式:左子树边0,右子树边1
#include<iostream> #include<string> #include<cstring> using namespace std; const int MaxW = 9999999; // 假设结点权值不超过9999999 // 定义huffman树结点类 class HuffNode { public: int weight; // 权值 int parent; // 父结点下标 int leftchild; // 左孩子下标 int rightchild; // 右孩子下标 }; // 定义赫夫曼树类 class HuffMan { private: void MakeTree(); // 建树,私有函数,被公有函数调用 void SelectMin(int pos, int* s1, int* s2); // 从 1 到 pos 的位置找出权值最小的两个结点,把结点下标存在 s1 和 s2 中 public: int len; // 结点数量 int lnum; // 叶子数量 HuffNode* huffTree; // 赫夫曼树,用数组表示 string* huffCode; // 每个字符对应的赫夫曼编码 void MakeTree(int n, int wt[]); // 公有函数,被主函数main调用 void Coding(); // 公有函数,被主函数main调用 void Destroy(); }; // 构建huffman树 void HuffMan::MakeTree(int n, int wt[]) { // 参数是叶子结点数量和叶子权值 // 公有函数,对外接口 int i; lnum = n; len = 2 * n - 1; huffTree = new HuffNode[2 * n]; huffCode = new string[lnum + 1]; // 位置从 1 开始计算 // huffCode实质是个二维字符数组,第 i 行表示第 i 个字符对应的编码 // 赫夫曼树huffTree初始化 for (i = 1; i <= n; i++) huffTree[i].weight = wt[i - 1]; // 第0号不用,从1开始编号 for (i = 1; i <= len; i++) { if (i > n) huffTree[i].weight = 0; // 前n个结点是叶子,已经设置 huffTree[i].parent = 0; huffTree[i].leftchild = 0; huffTree[i].rightchild = 0; } MakeTree(); // 调用私有函数建树 } void HuffMan::SelectMin(int pos, int* s1, int* s2) { // 找出最小的两个权值的下标 // 函数采用地址传递的方法,找出两个下标保存在 s1 和 s2 中 int w1, w2, i; w1 = w2 = MaxW; // 初始化w1和w2为最大值,在比较中会被实际的权值替换 *s1 = *s2 = 0; for (i = 1; i <= pos; i++) { if (huffTree[i].weight < w1 && huffTree[i].parent == 0) { w2 = w1; *s2 = *s1; w1 = huffTree[i].weight; *s1 = i; } else if (huffTree[i].weight < w2 && huffTree[i].parent == 0) { w2 = huffTree[i].weight; *s2 = i; } // 比较过程如下: // 如果第 i 个结点的权值小于 w1,且第 i 个结点是未选择的结点,提示:如果第 i 结点未选择,它父亲为 0 // 把第 w1 和 s1 保存到 w2 和 s2,即原来的第一最小值变成第二最小值 // 把第 i 结点的权值和下标保存到 w1 和 s1,作为第一最小值 // 否则,如果第 i 结点的权值小于 w2,且第 i 结点是未选择的结点 // 把第 i 结点的权值和下标保存到 w2 和 s2,作为第二最小值 } } void HuffMan::MakeTree() { // 私有函数,被公有函数调用 int i, s1, s2; for (i = lnum + 1; i <= len; i++) { SelectMin(i - 1, &s1, &s2); huffTree[s1].parent = i; huffTree[s2].parent = i; huffTree[i].leftchild = s1; huffTree[i].rightchild = s2; huffTree[i].weight = huffTree[s1].weight + huffTree[s2].weight; // 找出两个最小权值的下标放入 s1 和 s2 中 // 将找出的两棵权值最小的子树合并为一棵子树,过程包括 // 结点 s1 和结点 s2 的父亲设为 i // 结点 i 的左右孩子分别设为 s1 和 s2 // 结点 i 的权值等于 s1 和 s2 的权值和 } } // 销毁赫夫曼树 void HuffMan::Destroy() { len = 0; lnum = 0; delete[]huffTree; delete[]huffCode; } // 赫夫曼编码 void HuffMan::Coding() { char* cd; int i, c, f, start; // 求 n 个结点的赫夫曼编码 cd = new char[lnum]; // 分配求编码的工作空间 cd[lnum - 1] = '\0'; // 编码结束符 for (i = 1; i <= lnum; ++i) { // 逐个字符求赫夫曼编码 start = lnum - 1; // 编码结束符位置 for (c = i, f = huffTree[i].parent; f != 0; c = f, f = huffTree[f].parent) { if (huffTree[f].leftchild == c) { cd[--start] = '0'; } else { cd[--start] = '1'; } } // 参考课本P147算法6.12 HuffmanCoding代码 huffCode[i].assign(&cd[start]); // 把cd中从start到末尾的编码复制到huffCode中 } delete[]cd; // 释放工作空间 } // 主函数 int main() { int t, n, i, j; int wt[800]; HuffMan myHuff; cin >> t; for (i = 0; i < t; i++) { cin >> n; for (j = 0; j < n; j++)cin >> wt[j]; myHuff.MakeTree(n, wt); myHuff.Coding(); for (j = 1; j <= n; j++) { cout << myHuff.huffTree[j].weight << '-'; // 输出各权值 cout << myHuff.huffCode[j] << endl; // 输出各编码 } myHuff.Destroy(); } return 0; }
排序树
关键在于如何实现删除节点后重连接父节点和子节点
以及如何通过层序遍历建树
#include<iostream>
using namespace std;
class tree_node {
public:
tree_node* left;
tree_node* right;
tree_node* pare;
int data;
tree_node() :left(NULL), right(NULL),pare(NULL) {
}
tree_node(int d) :left(NULL), right(NULL), pare(NULL), data(d) {
}
};
class tree {
void insert_node(int d, tree_node* temp);
void delnode(int d,tree_node* temp);
public:
int count;
tree_node* root;
int len;
tree(int d) { root = new tree_node(d); }
void Insert(int d);
void midorder(tree_node* temp);
void Delnode(int d);
};
void tree::delnode(int d, tree_node* temp) {
tree_node* pre = new tree_node();
pre = temp;
while (temp) {
if (d > temp->data) {
pre = temp;
temp = temp->right;
}
else if (d < temp->data) {
pre = temp;
temp = temp->left;
}
else {
break;
}
}//找有没有d
if (temp) {//说明找到了
if (!temp->left &&! temp->right) {//左右都空
if (temp == root) {
root = NULL;
delete temp;
temp = NULL;
}
else {
pre->left == temp ? pre->left = NULL : pre->right = NULL;
delete temp;
temp = NULL;
}
}
else if (!temp->left&&temp->right) {//左空右不空
if (temp==root) {//要删的是根
tree_node* newnode = temp->right;
delete temp;
root = newnode;
}
else {
pre->left == temp ? pre->left = temp->right : pre->right = temp->right;
delete temp;
}
}
else if (!temp->right&&temp->left) {//右空左不空
if (temp == root) {//要删的是根
root = temp->left;
delete temp;
temp = root;
}
else {
pre->left == temp ? pre->left = temp->left : pre->right = temp->left;
delete temp;
}
}
else {//左右都有子树
tree_node* right_min = temp->right;
pre = right_min;
while (right_min->left != NULL) //最大里的最小结点
{
pre = right_min;
right_min = right_min->left;
}
temp->data = right_min->data; //最小结点的值赋给要删除的结点
if (right_min == pre)
{
temp->right = right_min->right;
}
else
{
pre->left = right_min->right;
}
delete right_min;
}
}
}
void tree::Delnode(int d) {
delnode(d, root);
}
void tree::midorder(tree_node* temp) {
if (temp) {
if(temp->left)midorder(temp->left);
cout << temp->data << " ";
if(temp->right)midorder(temp->right);
}
}
void tree::Insert(int d) {
insert_node(d, root);
}
void tree::insert_node(int d, tree_node* temp) {
if (d > temp->data && temp->right) {
insert_node(d, temp->right);
}
else if (d < temp->data && temp->left) {
insert_node(d, temp->left);
}
else if (d > temp->data && !temp->right) {
tree_node* new_node = new tree_node(d);
temp->right = new_node;
new_node->pare = temp;
}
else if (d < temp->data && !temp->left) {
tree_node* new_node = new tree_node(d);
temp->left = new_node;
new_node->pare = temp;
}
}
int main() {
int g;
cin >> g;
while (g--) {
int Numlen;
cin >> Numlen;
Numlen--;
int firstN;
cin >> firstN;
tree t(firstN);
while (Numlen--) {
int data;
cin >> data;
t.Insert(data);
}//扫完
t.midorder(t.root);
cout << endl;
cin >> Numlen;
while (Numlen--) {
int targ;
cin >> targ;
t.Delnode(targ);
t.midorder(t.root);
cout << endl;
}
}
}
图
子图:假设有两个图G=(V,E)和G1=(V1,E1);如果V1V,E1E,则称G1为G的子图。
完全图:任意两个顶点都有一条边相连。(指的是无向图)
无向完全图和有向完全图:对于无向图,若具有n(n-1)/2条边,则称为无向完全图;对于有向图,若具有n(n-1)条弧,则称为有向完全图。
稀疏图和稠密图:有很少条边或弧(如e<nlogn)的图称为稀疏图,反之称为稠密图
权和网:在实际应用中,每条边可以标上具有某种含义的数值,该数值称为该边上的权,这些权可以表示从一个顶点到另一个顶点的距离或耗费。这种带权的图通常称为网。
邻接点:对于无向图G,如果图的边(v, v1)E,则称顶点v和v1互为邻接点,即v和v1相邻接。
关联(依附):边/弧与顶点之间的关系;边(v, v')依附于顶点v和v1,或者说边(v, v1)与顶点v和v1相关联。
顶点的度:与该顶点相关联的边的数目,记为TD(v);在有向图中,顶点的度等于该顶点的入度与出度之和,顶点v的入度是以v为终点的有向边的条数,记作ID(v),顶点的出度是以v为始点的有向边的条数,记作OD(v)。
路径:接续的边构成的顶点序列
路径长度:路径上边或弧的数目/权值之和
回路(环):第一个顶点和最后一个顶点相同的路径
简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径
简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径。
连通:两个顶点之间有路径,则称这两个顶点是连通的。
连通图:对于图中任意两个顶点都是连通的,则称该图是连通图
强连通图:在有向图中对于任意两个顶点是连通的,则称该图是强连通图。
连通分量:指的是无向图中的极大连通子图(极大连通子图意思为该子图是G的连通子图,将G的任何不在该子图中的顶点加入,子图不再连通)
强连通分量:有向图的极大强连通子图
极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边子图不再连通
连通图的生成树:包含无向图所有顶点的极小连通子图
有向树:有一个顶点的入度为0,其余顶点的入度均为1的有向图称为有向树
生成森林:对于非连通图,由各个连通分量的生成树的集合
————————————————
版权声明:本文为CSDN博主「凌晨四点半sec」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_65049289/article/details/128248224
图算法
深度优先搜索
void map::dfs(int v)
{
visit[v] = 1;
cout << v << " ";
int* vex = new int[vexnum];
//初始化新路径数组
for (int i = 0; i < vexnum; i++)
{
vex[i] = -1;
}
int cnt = 0;
for (int i = 0; i < vexnum; i++)
{
if (graph[v][i])
{
vex[cnt++] = i;
}
}
cnt = 0;
int temp = vex[cnt];
while (temp != -1)
{
if (!visit[temp])
{
dfs(temp);
}
temp = vex[++cnt];
}
delete []vex;
}
广度优先搜索
void map::bfs(int v)
{
queue<int>q;
cout << v << " ";
visit[v] = 1;
q.push(v);
while (!q.empty())
{
int temp = q.front();
q.pop();
int* vex = new int[vexnum];
//初始化下一数组
for (int i = 0; i < vexnum; i++)
{
vex[i] = -1;
}
int cnt = 0;
for (int i = 0; i < vexnum; i++)
{
if (graph[temp][i])
{
vex[cnt++] = i;
}
}
int temp1;
for (int i = 0; i < cnt; i++)
{
temp1 = vex[i];
if (!visit[temp1])
{
visit[temp1] = 1;
cout << temp1 << " ";
q.push(temp1);
}
}
}
cout << endl;
}
排序
希尔排序:外两层间隔gap使用序列长度循环除2直到1,然后分组插入排序
void Sort(vector<int>& v) {
int len = v.size();
for (int gap = len / 2; gap > 0; gap /= 2) {
for (int i = 0; i < gap; i++) {//组数
for (int j = i; j < len; j += gap) {//选择排序
for (int k = j; k > 0; k -= gap) {
if (k - gap >= 0 && v[k] > v[k - gap]) {
int temp = v[k];
v[k] = v[k - gap];
v[k - gap] = temp;
}
else break;
}
}
}
}
选择排序:选一个最小,再在后序列寻找更小的交换
void sele_sort(vector<int>&v) {
int len=v.size();
for (int i = 0; i < len - 1; i++) {
int min = i;
for (int j = i + 1; j < len; j++) {
if (v[j] < v[min]) {
min = j;
}
}
swap(v[i], v[min]);
}
}
插入排序:外循环i从0到尾,内循环从i到0(内部排好序swap)
void ins_sort(vector<int>& v) {
int len = v.size();
for (int j = 0; j < len; j++) {//选择排序
for (int k = j; k > 0; k--) {
if (k - 1 >= 0 && v[k] < v[k - 1]) {
int temp = v[k];
v[k] = v[k - 1];
v[k - 1] = temp;
}
}
}
}
快速排序
int qsort2(int low, int high)
{
int key = nums[low];
while (low < high)
{
while (low < high && key <= nums[high])
high--;
swap(nums[low], nums[high]);
while (low < high && key >= nums[low])
low++;
swap(nums[low], nums[high]);
}
return low;
}
void qsort1(int low, int high)
{
int zhou;
if (low < high)
{
zhou = qsort2(low, high);
qsort1(low, zhou - 1);
qsort1(zhou + 1, high);
}
}
冒泡排序
void bub_sort(vector<int>& v) {
int len = v.size();
int cnt = 0;
for (int i = 0; i < len; i++) {
for (int j = 0;j<len-i-1; j++) {
if (v[j] > v[j + 1]) {
int temp = v[j];
v[j] = v[j + 1];
v[j + 1] = temp;
cnt++;
}
}
}
cout << cnt << endl;
}
堆排序:
void Heap(int* nums, int loc, int n)
{
int left = 2 * loc + 1;
int right = 2 * loc + 2;//设置左右子树
if (left < n)
{
if (right < n)
{
if (nums[left] > nums[right])
{
if (nums[right] < nums[loc])
{
swap(nums[right], nums[loc]);
Heap(nums, right, n);
}
}
else
{
if (nums[left] < nums[loc])
{
swap(nums[left], nums[loc]);
Heap(nums, left, n);
}
}
}
else
{
if (nums[left] < nums[loc])
{
swap(nums[left], nums[loc]);
Heap(nums, left, n);
}
}
}
}
void Sort(int n)
{
int* nums;
nums = new int[n];
for (int i = 0; i < n; i++)
{
cin >> nums[i];
}
for (int i = n / 2; i >= 0; i--)
{
Heap(nums, i, n);
}//建heap
dis_heap(n, nums);
for (int t = n - 1; t >= 1; t--)
{
swap(nums[0], nums[t]);
Heap(nums, 0, t);
dis_heap(n, nums);
}
}
/*建立左右节点
判断如果有左子树(否则连子树都没有)
如果有右子树(左右都有)
如果左大于右
如果右小于父
交换右父(最小在上)
递归右
如果左不大于右
如果左小于于父
交换
递归左
如果没有右子树(单左)
判断左节点和父节点交换*/
/*主函数sort:
for(int i=len/2;i>=0;i--)建堆
for (int t = lem - 1; t >= 1; t--)
{
swap(nums[0], nums[t]);
Heap(nums, 0, t);
dis_heap(n, nums);
}
*/