重点
一、数据结构的定义
逻辑结构
集合结构:除了同属于一个集合之外,没有其他关系
线状结构:数据元素之间是一对一的关系
树形结构:数据元素之间是一对多的层次关系
图形结构:数据元素之间是多对多的关系
存储结构
线性存储结构:数据元素存放在连续的存储单元里,其数据间的逻辑关系和物理关系是一致的
链式存储结构:数据元素存放在任意的存储单里,存储单元可以是连续的,也可以是不连续的
分析算法的世界复杂度和空间复杂度
二、线性表
1、顺序表和链表的优缺点
顺序表:
优点:
- 顺序表的内存空间连续。
- 尾插、尾删效率较高,时间复杂度是O(1)。
- 支持随机访问,可以高效的按下标进行操作,时间复杂度是O(1)。
缺点:
- 在顺序表中间插入或删除元素时都涉及到元素的移动,效率较低,时间复杂度为O(N)。
- 顺序表长度固定,有时需要扩容。
链表 :
优点:
-
链表的内存空间不连续。
-
如果知道要处理节点的前一个位置,则进行插入和删除的复杂度为O(1);
-
如果不知道要处理节点的前一个位置,则进行插入和删除的复杂度为O(N)。
-
头插、头删的效率高,时间复杂度是O(1)。
-
没有空间限制,不会溢出,可以存储很多元素。
缺点:
链表不支持随机访问,查找元素效率低,需要遍历节点,时间复杂度是O(n)。
2、考察链表或者双向链表
1.带头还是不带头
2.循环还是不循环
3.单向还是双向
- 虽然单链表的结构众多,但大部分常用的还是两种结构。无头单向不循环链表和带头双向循环链表
例题
约瑟夫环
将链表各个元素排在一个圆上,从头开始数,数到第k个元素就删掉,直到链表只有一个元素为止。
struct node
{int data;node *next;
};
node *creat(int n)
{node *head, *tail, *p;head = new node;tail = new node;p = new node;p -> data = 1;p -> next = NULL;head = p;tail = p;for(int i = 2; i <= n; i ++){p = new node;p -> data = i;p -> next = NULL;tail -> next = p;tail = p;}tail -> next = head;return head;
}
void del(node *head, int n, int m)
{int cnt = 0, s = 0;node *pre, *p;pre = head;while(pre -> next != head){pre = pre -> next;}while(cnt < n - 1){p = pre -> next;s ++;if(s == m){s = 0;cnt ++;pre -> next = p -> next;cout << p -> data << " ";delete(p);}else pre = p;}cout << pre -> data << endl;
}
单链表为啥要设置头节点
- 有了头结点后****,对在第一个元素结点前插入结点和删除第一个结点,其操作与对其它结点的操作统一了。****
- 头指针具有标识作用,故常用头指针冠以链表的名字。
- *为了使空链表与非空链表处理一致,我们通常设一个头结点*。
三、栈与队列
栈
入出栈都采取先进后出原则。
中缀式转成后缀式
/*
首先输入字符串,然后遍历字符串进行操作
当当前字符为数字时直接输出
当当前字符为*或/时,输出栈中的*或/号,当前字符入栈
当当前字符为+或-时,输出栈中的字符直到字符为(为止,当前字符入栈
当当前字符为(时,入栈
当当前字符为)时,输出栈中字符直到(为止
*/
后缀式求值
设置一个栈,开始时,栈为空,然后从左到右扫描后缀表达式,若遇操作数,则进栈;若遇运算符,则从栈中退出两个元素,先退出的放到运算符的右边,后退出的 放到运算符左边,运算后的结果再进栈,直到后缀表达式扫描完毕
5 -2 + 3 * #
遇到5, -2,就push到栈中,此时栈中有两个元素,为5, -2,遇到+,从栈顶中取出两个元
素,进行运算,5 +(-2) = 3,获得结果之后,将结果push到栈中,现在栈中只剩下了一个元素
3,然后继续输入,3,push到栈中,此时栈中有两个元素,遇到 ‘*’ 号,从栈顶中取出两个元素,
3, 3,进行乘法运算,3 * 3 = 9,然后将 9 push到栈中,栈中只剩下了一个元素,9,然后继续
输入,此时遇到了 ‘#’ 号,结束读入。
括号匹配
当当前字符为(、【、{时,将括号入栈,当当前字符为)、】、}时,查看栈顶元素是否为其对应的括号,同时栈不可以为空
队列
队列是先进先出
用队列解决约瑟夫环问题
将所有元素入队列,
int main()
{int n, m;cin >> n >> m;queue<int>q;for(int i = 1; i <= n; i ++)q.push(i);while(q.size() > 1){for(int i = 0; i < m - 1; i ++){int now = q.front();q.pop();q.push(now);}cout << q.front() << " ";q.pop();}cout << q.front() << endl;return 0;
}
四、kmp
模板
string s, t;
int n, m;
int Next[N];
void dp()
{int i = 0, j = -1;Next[0] = -1;while(i < m){if(j == -1 || t[i] == t[j]){i ++;j ++;Next[i] = j;}else j = Next[j];}
}
int kmp()
{int i = 0, j = 0;int cnt = 0;while(i < n && j < m){if(j == -1 || s[i] == t[j]){i ++;j ++;}else j = Next[j];if(j == m){cnt ++;j = Next[j];}}return cnt;
}
五、广义表知识点
广义表的基础概念
-
什么是广义表
广义表,又称列表,也是一种线性存储结构,既可以存储不可再分的元素,也可以存储广义表,记作:LS = (a1,a2,…,an),其中,LS 代表广义表的名称,an 表示广义表存储的数据,广义表中每个 ai 既可以代表单个元素,也可以代表另一个广义表。
-
广义表的原子和子表
广义表中存储的单个元素称为 “原子”,而存储的广义表称为 “子表”。
例如 :广义表 LS = {1,{1,2,3}},则此广义表的构成 :广义表 LS 存储了一个原子 1 和子表 {1,2,3}。
广义表存储数据的一些常用形式:
A = ():A 表示一个广义表,只不过表是空的。
B = (e):广义表 B 中只有一个原子 e。
C = (a,(b,c,d)) :广义表 C 中有两个元素,原子 a 和子表 (b,c,d)。
D = (A,B,C):广义表 D 中存有 3 个子表,分别是A、B和C。这种表示方式等同于 D = ((),(e),(b,c,d)) 。
E = (a,E):广义表 E 中有两个元素,原子 a 和它本身。这是一个递归广义表,等同于:E = (a,(a,(a,…)))。 -
广义表的表头和表尾
当广义表不是空表时,称第一个数据(原子或子表)为"表头",剩下的数据构成的新广义表为"表尾"。
除非广义表为空表,否则广义表一定具有表头和表尾,且广义表的表尾一定是一个广义表。
广义表的存储结构
求广义表长度时,两种不同的存储方式求解也有所不同,如下示意图所示:
对于图 1a) 来说,只需计算最顶层(红色标注)含有的节点数量,即可求的广义表的长度。同理,对于图 1b) 来说,由于其最顶层(蓝色标注)表示的此广义表,而第二层(红色标注)表示的才是该广义表中包含的数据元素,因此可以通过计算第二层中包含的节点数量,才可求得广义表的长度。
六、树和二叉树
先序中序后序
根左右,左根右,左右根
层序遍历
从上到下,从左到右遍历
void cengxu(node *root)
{int in = 0, out = 0;node *q[55];q[in ++] = root;while(in > out){if(q[out]){cout << q[out] -> data;q[in ++] = q[out] -> l;q[in ++] = q[out] -> r;}out ++;}
}
以上算法都是时间复杂度o(n),空间复杂度o(n)
还原二叉树、创建二叉树
还原二叉树
先序遍历字符串输入,如果为叶子节点输出NULL,否则创造新节点,左建树,右建树
struct node
{int data;node *l, *r;
};
char a[N];
int k = 0;
node *build()
{node *root;if(a[k ++] == ',') return NULL;root = new node;root -> data = a[k];root -> l = build();root -> r = build();return root;
}
创建二叉树
先序中序输出后序
struct node
{int data;node *l, *r;
};
char pre[N], mid[N];
node *build(int len, char *pre, char *mid)
{if(!len) return NULL;node *root = NULL;root = new node;root -> data = pre[0];int i;for(i = 0; i < len; i ++){if(mid[i] == pre[0])break;}root -> l = build(i, pre + 1, mid);root -> r = build(len - i - 1, pre + i + 1, mid + 1 + i);return root;
}
void postorder(node *root)
{if(root){postorder(root -> l);postorder(root -> r);cout << char(root -> data);}
}
中序和后序输出先序
struct node
{int data;node *l, *r;
};
char mid[N], post[N];
node *build(int len, char *mid, char *post)
{if(!len) return NULL;node *root = NULL;root = new node;root -> data = post[len - 1];int i;for(i = 0; i < len; i ++){if(mid[i] == post[len - 1])break;}root -> l = build(i, mid, post);root -> r = build(len - 1 - i, mid + i + 1, post + i);return root;
}
void preorder(node *root)
{if(root){cout << char(root -> data);preorder(root -> l);preorder(root -> r);}
}
二叉树的六个性质
性质1:二叉树第i层上的结点数目最多为2^(i-1)(i>=1)
性质2:深度为i的二叉树至多有2 ^(i)-1个结点,至少有2 ^(i-1)个结点(i>=1)
性质3:包含n个结点的二叉树的高度至少为
( l o g 2 n ) + 1 (log2 n)+1 (log2n)+1
性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1
证明:
n为总结点数,n1为度为1的结点总数,n0,n2同理;
由(1)(2)
n=n0+n1+n2 (1)
n=n0 *0+n1 *1+n2 2+1即 n=n1+2n2+1 (2)
得:n0=n2+1
性质5:具有n个结点的完全二叉树的深度为
f l o o r ( l o g 2 n ) ( 向下取整 ) + 1 floor(log2n)(向下取整)+1 floor(log2n)(向下取整)+1
性质6:将一颗完全二叉树依次编号1-n;
结点编号间关系:
floor(i/2)|i/ \ 2i 2i+1
求深度求叶子
求深度
int deep(node *root)
{int d1, d2;if(root){d1 = deep(root -> l);d2 = deep(root -> r);return max(d1, d2) + 1;}return 0;
}
求叶子
出度为0的为叶子
树与二叉树,树二叉树和森林之间的转化
-
将树转换为二叉树:树中每个结点最多只有一个最左边的孩子(长子)和一个右邻的兄弟。按照这种关系很自然地就能将树转换成相应的二叉树:1.在所有兄弟结点之间加一连线2.对每个结点,除了保留与其长子的连线外,去掉该结点与其它孩子的连线。如下图所示:
-
将一个森林转换为二叉树:
具体方法是:1.将森林中的每棵树变为二叉树;2.因为转换所得的二叉树的根结点的右子树均为空,故可将各二叉树的根结点视为兄弟从左至右连在一起,就形成了一棵二叉树。
如下图所示:
-
二叉树转换为树:
是树转换为二叉树的逆过程。
1.加线。若某结点X的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点…,都作为结点X的孩子。将结点X与这些右孩子结点用线连接起来。
2.去线。删除原二叉树中所有结点与其右孩子结点的连线。
如下图所示:
-
二叉树转换为森林:
假如一棵二叉树的根节点有右孩子,则这棵二叉树能够转换为森林,否则将转换为一棵树。
1.从根节点开始,若右孩子存在,则把与右孩子结点的连线删除。再查看分离后的二叉树,若其根节点的右孩子存在,则连线删除…。直到所有这些根节点与右孩子的连线都删除为止。
2.将每棵分离后的二叉树转换为树。
如下图所示:
七、图论
基本算法:图的邻接矩阵存储,图的邻接表存储,图的正向反向转化,会画邻接矩阵邻接表,有向图会画逆邻接表,还原图,bfs(分层,dp,最短路)和dfs(图的连通集)的算法
最小生成树的prime算法和克鲁斯卡尔算法
最短路的迪杰斯特拉算法,floyd, 拓扑排序算法,欧拉回路
邻接矩阵和邻接表存储知识点
邻接矩阵就是一个二维数组存储信息
邻接表就是vector<vector>mp;
邻接矩阵适用于稠密图(边数接近于顶点数的平方),邻接表适用于稀疏图(边数远小于顶点数的平方)。
bfs
存储
BFS是一种借用队列来存储的过程,分层查找,优先考虑距离出发点近的点。无论是在邻接表还是邻接矩阵中存储,都需要借助一个辅助队列,v个顶点均需入队,最坏的情况下,空间复杂度为O(v)。
邻接表形式存储时,每个顶点均需搜索一次,时间复杂度T1=O(v),从一个顶点开始搜索时,开始搜索,访问未被访问过的节点。最坏的情况下,每个顶点至少访问一次,每条边至少访问1次,这是因为在搜索的过程中,若某结点向下搜索时,其子结点都访问过了,这时候就会回退,故时间复 杂度为O(E),算法总的时间复 度为O(|V|+|E|)。
邻接矩阵存储方式时,查找每个顶点的邻接点所需时间为O(V),即该节点所在的该行该列。又有n个顶点,故算总的时间复杂度为O(|V|^2)。
算法实现
将起点放到队列里,从队列头节点开始遍历,将与他连着的点放到队列里,然后继续遍历队列头节点,重复之前的,把与他连着的点放到队列里,最后遍历完成,全部节点入队
void bfs(int op)
{queue<int>q;q.push(op);vis[op] = 1;while(q.size()){int now = q.front();q.pop();cout << now << " ";for(int i = 0; i < mp[now].size(); i ++){if(!vis[mp[now][i]]){vis[mp[now][i]] = 1;q.push(mp[now][i]);}}}
}
dfs
存储
DFS算法是一一个递归算法,需要借助一个递归工作栈,故它的空问复杂度为O(V)。
遍历图的过程实质上是对每个顶点查找其邻接点的过程,其耗费的时间取决于所采用结构。
邻接表表示时,查找所有顶点的邻接点所需时间为O(E),访问顶点的邻接点所花时间为O(V),此时,总的时间复杂度为O(V+E)。
邻接矩阵表示时,查找每个顶点的邻接点所需时间为O(V),要查找整个矩阵,故总的时间度为O(V^2)。
v为图的顶点数,E为边数。
算法实现
将起点放到队列里,然后遍历队列,取出头节点,搜索与他连着的点,然后搜索与他连着的点的连着的点,然后是连着的点的连着的点的连着的点。。。。。
vector<int>mp[N];
int vis[N];
int n, m;
void dfs(int op)
{if(op > n) return;cout << op << " ";vis[op] = 1;for(int i = 0; i < mp[op].size(); i ++){if(!vis[mp[op][i]]){dfs(mp[op][i]);}}
}
int main()
{cin >> n >> m;for(int i = 1; i <= m; i ++){int u, v;cin >> u >> v;mp[u].push_back(v);}for(int i = 0; i < n; i ++)sort(mp[i].begin(), mp[i].end());for(int i = 0; i < n; i ++){if(!vis[i]){dfs(i);}}return 0;
}
最小生成树
克鲁苏卡尔
将所有边排序,从小到大依次加边,加边过程中不可以成环,直到加完n-1条边
struct node
{int u, v, w;
}a[N];
int f[N];
bool cmp(node a, node b)
{return a.w < b.w;
}
int Find(int x)
{return x == f[x] ? x : f[x] = Find(f[x]);
}
int Merge(int x, int y)
{int a = Find(x);int b = Find(y);if(a != b){f[b] = a;return 1;}return 0;
}
int main()
{int n, m;cin >> n >> m;for(int i = 0; i <= n; i ++)f[i] = i;for(int i = 1; i <= m; i ++){int u, v, w;cin >> u >> v >> w;a[i].u = u;a[i].v = v;a[i].w = w;}sort(a + 1, a + 1 + m, cmp);int cnt = 0, ans = 0;for(int i = 1; i <= m; i ++){int x = a[i].u, y = a[i].v;if(Merge(x, y)){cnt ++;ans += a[i].w;}}if(cnt != n - 1) puts("-1");else cout << ans << endl;return 0;
}
普利姆算法
从起点开始遍历,将这个点看作一个集合,然后找离这个集合最近的一个点,然后将这个点放入集合,然后再重复操作。
int mp[N][N];
int vis[N];
int dis[N];
int n, m;
int prime()
{mem(dis, INF);dis[1] = 0;int ans = 0;for(int i = 1; i <= n; i ++){int u = -1, Min = INF;for(int j = 1; j <= n; j ++){if(!vis[j] && dis[j] < Min){u = j;Min = dis[j];}}if(u == -1) return -1;vis[u] = 1;ans += dis[u];for(int j = 1; j <= n; j ++){if(!vis[j] && mp[u][j] != INF && dis[j] > mp[u][j]){dis[j] = mp[u][j];}}}return ans;
}
int main()
{mem(mp, INF);cin >> n >> m;while(m --){int u, v, w;cin >> u >> v >> w;mp[u][v] = w;mp[v][u] = w;}cout << prime();return 0;
}
最短路
迪杰斯特拉
朴素版迪杰斯特拉 O(n^2)
vector<PII>mp[20010];
int dis[20010], vis[20010];
int n, m;
void dijkstra()
{mem(dis, 0x3f);dis[0] = 0;for(int i = 1; i <= n; i ++){int u = -1, Min = 999999;for(int j = 0; j < n; j ++){if(!vis[j] && Min > dis[j]){u = j;Min = dis[j];}}if(u == -1) break;vis[u] = 1;for(int j = 0; j < mp[u].size(); j ++){int v = mp[u][j].xx;int w = mp[u][j].yy;if(!vis[v]){dis[v] = min(dis[v], dis[u] + w);}}}
}
int main()
{cin >> n >> m;while(m --){int u, v, w;cin >> u >> v >> w;mp[u].push_back({v, w});}dijkstra();for(int i = 1; i < n; i ++){if(dis[i] != INF){cout << dis[i] << " ";}}return 0;
}
floyd
for(int k = 1; k <= n; k ++){for(int i = 1; i <= n; i ++){for(int j = 1; j <= n; j ++){f[i][j] = min(f[i][j], f[i][k] + f[k][j]);}}}
拓扑排序
将所有点统计入度,入度为0的点为根节点,将根节点入队列,然后取出头节点,删去头节点,将头节点连着的每一个点入度都减一,当减到0时入队,直到元素遍历完,入队顺序就是拓扑顺序。
时间复杂度:O(n + e)
空间复杂度:O(n)
void toop()
{queue<int>q;for(int i = 0; i < n; i ++){if(in[i] == 0)q.push(i);}int cnt = 0, ans = 0;while(q.size()){int now = q.front();q.pop();cnt ++;for(int i = 0; i < n; i ++){if(mp[now][i] != -1){in[i] --;if(in[i] == 0)q.push(i);dis[i] = max(dis[now] + mp[now][i], dis[i]);ans = max(dis[i], ans);}}}if(cnt != n) puts("Impossible");else cout << ans << endl;
}
欧拉回路
度全都是偶数即为欧拉图
八、查找表
静态:折半;动态:二叉排序树
二分查找
//分成[l, mid][mid + 1, r]找min
int erfen()
{int l = 0, r = INF;while(l < r){int mid = l + r >> 1;if(check(mid))r = mid;else l = mid + 1;}return l;
}
// 分成[l, mid - 1][mid, r], 找max
int erfen()
{int l = 0, r = INF;while(l < r){int mid = l + r + 1 >> 1;if(check(mid))l = mid;else r = mid - 1;}return l;
}
二叉排序树
左边的儿子一定比根节点小,右边的儿子大于等于根节点
查找
//查找的递归算法
BSTNode *Search(BSTNode *root, int x)
{if(root->data == x){return root;}else if(x < root->data){return Search(root->left, x);}else{return Search(root->right, x);}
}
插入
//插入的递归算法
BSTNode *Insert(BSTNode *root, int x){if(root == NULL){root = CreateTreeNode(x);return root;}if(x < root->data){root->left = Insert(root->left, x);}if(x > root->data){root->right = Insert(root->right, x);}return root;
}
删除
- 删叶子节点:直接删
- 删有一个子树的:把子树连到要删除的节点的根节点上
- 删有两个节点的:找到右子树的最小值,然后用1、2操作删除它,然后安到我们要删的节点上
优秀博客:如何从二叉搜索树中删除节点? (baidu.com)
平均查找长度
-
查找成功的平均查找长度
-
查找不成功的平均查找长度
九、哈希表
哈希的存储,线性探测,平方探测,拉链探测;查找成功的平均长度,查找不成功的平均长度,ALS
参考( 哈希.docx)
当哈希表后边没表格的时候,再从头开始找
平均查找长度
精彩博客:哈希表平均查找长度_数据结构哈希表查找长度_好饿呀~~~的博客-CSDN博客
十、排序
基本思想:冒泡,插入,希尔,快排,堆排,归并排序,桶排,基数排序 的思想以及应用,优先级的比较,复杂度的比较,总体性能的比较,对序列初始状态的要求,稳定性的问题。
1.基本流程
快排
i指针放l,j指针放r,j–,直到比al小的时候停止,i++,直到比al大的时候停止,然后互换,最后互换ai和al,让al搁中间,然后再递归排序两边的序列
void q_sort(int l, int r)
{int i = l, j = r;if(l > r) return;while(i < j){while(a[j] >= a[l] && i < j)j --;while(a[i] <= a[l] && i < j)i ++;if(i < j) swap(a[i], a[j]);}swap(a[i], a[l]);q_sort(l, i - 1);q_sort(i + 1, r);
}
归并排序
分成单个区间,然后合并的时候两两排序,直到排好
int tmp[N], a[N], n;
//这里为需要背诵的部分,要求:给一个相关题目,能快速写出该模板并调试通过。
//归并排序
void merge_sort(int q[], int l, int r)
{//递归出口if (l >= r) return;//第一步,分成两个子区间int mid = l + r >> 1;//第二部,递归处理子区间//要注意这里用的mid和mid+1来划分两区间,建议不要用mid-1和mid来划分merge_sort(q, l, mid);merge_sort(q, mid + 1, r);//第三步,合并排序好的子区间//tips:k为tmp下标,i和j为两个子区间起始位置int k = 0, i = l, j = mid + 1;//排序好的两边取大小,暂存数组tmp挑小的取while (i <= mid && j <= r) {if (q[i] <= q[j]) tmp[k++] = q[i++];else tmp[k++] = q[j++];}//很可能存在有一子区间没有比较完,由于该区间是排好序的,后面的没比较完,说明都是最大的,直接往tmp后面加即可。while (i <= mid) tmp[k++] = q[i++];while (j <= r) tmp[k++] = q[j++];//在[l, r]范围中,将tmp数组存的有序值赋给q数组完排序,注意<=r的等号不要漏for (int i = l, j =0; i <= r; i++, j++) q[i] = tmp[j];
}
堆排序
1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
注意:升序用大根堆,降序就用小根堆(默认为升序)
int cnt;
int h[N];
void down(int x)
{int t = x;if (x * 2 <= cnt && h[x * 2] > h[t])t = x * 2;if (x * 2 + 1 <= cnt && h[x * 2 + 1] > h[t])t = x * 2 + 1;if (x != t){swap(h[x], h[t]);down(t);}
}
void heap_sort(int l, int r)
{cnt = 0;for (int i = l; i <= r; i++){h[++cnt] = a[i];}for (int i = cnt / 2; i; i--){down(i);}int k = r;while (cnt){a[k --] = h[1];h[1] = h[cnt--];down(1);}
}
计数排序
计算ai的数量,然后算它在数组中是第几小的,然后把第几小赋给b数组作为下标,同时b下标=a中的值,最后整理下
int a[N], h[N+50];
int b[N];
int n;//如果出现负数 (-10000,10000)整体右移 x+10000 (0,20000)
//如果小数 整体*100
void counting_sort()
{int w = 100050;memset(h, 0, sizeof h);for (int i = 0; i < n; i++) h[a[i]]++;for (int i = 1; i <= w; i++) h[i] += h[i - 1];for (int i = n-1; i >= 0; i--) b[h[a[i]]--] = a[i];for(int i=1;i<=n;i++)a[i-1]=b[i];
}
桶排序
桶排序的核心思想就是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶排序完之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。
int n, w = 100000, a[N];
vector<int> bucket[N];void insert_sort(vector<int>& A)
{for (int i = 1; i < A.size(); i++) {int key = A[i];int j = i - 1;while (j >= 0 && A[j] > key) {A[j + 1] = A[j];j--;}A[j + 1] = key;}
}
void bucket_sort()
{int bucket_size = w / n + 1;for (int i = 0; i < n; i++) {bucket[i].clear();}for (int i = 0; i < n; i++) {bucket[a[i] / bucket_size].push_back(a[i]);}int p = 0;for (int i = 0; i < n; i++) {insert_sort(bucket[i]);for (int j = 0; j < bucket[i].size(); j++) {a[p++] = bucket[i][j];}}
}
// I prefer it
vector<int> bucket[N];
int w = 100000;
void bucket_sort()
{int bucket_size = w/n + 1;for(int i = 0; i < n; i ++)bucket[i].clear();for(int i = 0; i < n; i ++){bucket[a[i]/bucket_size].push_back(a[i]);}int p = 0;for(int i = 0; i < n; i ++){sort(bucket[i].begin(), bucket[i].end());for(auto it : bucket[i])a[p ++] = it;}
}
基数排序
const int N = 100010;
const int W = 100010;
const int K = 100;
int n, w[K], k, cnt[W];
struct Element {int key[K];bool operator<(const Element& y) const {for (int i = 1; i <= k; ++i) {if (key[i] == y.key[i]) continue;return key[i] < y.key[i];}return false;}
} a[N], b[N];
void counting_sort(int p) {memset(cnt, 0, sizeof(cnt));for (int i = 1; i <= n; ++i) ++cnt[a[i].key[p]];for (int i = 1; i <= w[p]; ++i) cnt[i] += cnt[i - 1];for (int i = n; i >= 1; --i) b[cnt[a[i].key[p]]--] = a[i];memcpy(a, b, sizeof(a));
}
void radix_sort() {for (int i = k; i >= 1; --i) {counting_sort(i);}
}
2.各种算法的特点
堆排序、快速排序、希尔排序、直接选择排序是不稳定的排序算法,而冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。