栈stack(后进先出)
没啥可说的吧
队列queue(先进先出)
同上
链表Link
微笑
散列表(哈希表)
建立hash和key的对应关系,出现冲突了要有冲突解决机制。
构造哈希函数的方式:除留余数法、直接定址法、折叠法、平方取值法、数字分析法
处理冲突:开放定制法(线性探测法【查看下一个单元能不能用】、平方探测法【不容易产生堆积】、再散列法、伪随机序列法),拉链法(相同的值存在同一个链表里)
KMP
看猫片,懂的都懂,直接上代码好了
public class KMP {
/**
* 求出一个字符数组的next数组
* @param t 字符数组
* @return next数组
*/
public static int[] getNextArray(char[] t) {
int[] next = new int[t.length+1];
next[1] = 0;
next[2] = 1;
int i=2,j=1;
while(i<t.length){
if(j==0 || t[i-1]=t[j-1]){
i++;
j++;
next[i] = j;
}else {
j = next[j];
}
}
return next;
}
/**
* 对主串s和模式串t进行KMP模式匹配
* @param s 主串
* @param t 模式串
* @return 若匹配成功,返回t在s中的位置(第一个相同字符对应的位置),若匹配失败,返回-1
*/
public static int kmpMatch(String s, String t){
char[] s_arr = s.toCharArray();
char[] t_arr = t.toCharArray();
int[] next = getNextArray(t_arr);
int i = 1, j = 1;
while (i<=s_arr.length && j<=t_arr.length){
if(j == 0 || s_arr[i-1]==t_arr[j-1]){
i++;
j++;
}
else
j = next[j];
}
if(j > t_arr.length)
return i-t.length;
else
return 0;
}
public static void main(String[] args) {
System.out.println(kmpMatch("abcabaabaabcacb", "abaabcac"));
}
}
排序二叉树
左子树<根节点<右子树
平衡二叉树(AVL)
排序二叉树的特例,但是左右子树高度差不能超过1.为了解决排序二叉树的线性问题,出现了平衡二叉树
LL.单旋转
LR 左旋+右旋
RR 单旋转
RL 右旋+左旋
红黑树
排序二叉树的变种。有存储位来专门存储颜色。红黑树就是为了进一步解决排序二叉树的线性问题。
根节点是黑色,叶子结点为黑色(null或者nil节点,不存储数据),如果一个节点为红色,那他的子节点必须是黑色的,一个节点到该节点的子孙节点的所有路径上包含相同数量的黑节点(黑色完美平衡)
通过左旋,右旋,变色达到自平衡
跳表
SkipList,以空间换时间,在原链表的基础上形成多层索引,但是某个节点在插入时,是否成为索引,随机决定,所以跳表又称为概率数据结构。
时间复杂度接近红黑树,但是代码量比红黑树少很多。
redis就使用了跳表
但是为什么hashmap没用呢。是因为hashmap本身的空间利用率很低,再利用跳表,空间利用率会极具下降。
问:有一个链表结构,如何提高访问速度:跳表
哈希环
在2^32-1上进行哈希,一般进行两次哈希,key哈希,机器编号也要进行一次hash。从而确定key的匹配空间。memecache用到了。
位图bitmap
常常基于二进制数组实现,用一个bit标志一个数字是否存在。redis的特殊数据类型用到了。
败者树
完全二叉树。叶子结点存储数据,中间结点存储失败者的号码,待排序的多个队列,分别往不同的叶子结点输入数据,经过竞争比较,最终输出的是胜利者(最小的节点)
哈夫曼树
M叉树。并且WPL最小。
B-tree
平衡多路查找树。没啥可说的。就是同一行(非叶子节点),既要存数据,又要存索引。叶子结点只存储信息。只能通过主键范围查找。
B+Tree
非叶子节点只存储键值信息。
所有叶子节点之间都有一个链指针。
数据记录都存放在叶子节点中。
有两种查找方式:根节点随机查找,主键范围查找
InnoDB存储引擎就是用B+Tree实现其索引结构。
聚集索引和辅助索引。(辅助索引存的是主键id,再用主键id去聚集索引查值)
查找
顺序查找: O(n)
折半查找: O(log2n)
分块查找:创建一个索引表,块内部可以无序,但是块与块之间是有序的。先折半后顺序
b树:平衡多路查找树
b+树: 改进的平衡多路查找树
散列表: O(1)
插入排序
直接插入排序:找到位置,插入 o(logn2)。稳定排序。
希尔排序:逐渐缩小增量排序o(n1.3)~o(n2) 不稳定。
核心思路是取一个步长D。距离相同步长的数据先进行一次直接插入排序,然后再取步长D2(D2小于D1),再进行一次直接插入排序…
折半插入排序 找到位置的过程使用折半,插入。移动次数不变 o(nlog2n)
交换排序
冒泡排序:双层for循环,交换位置,每次确定一个元素的最终位置 o(logn2),稳定排序。
快速排序:
public class QuickSort {
public static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;
j=high;
//temp就是基准位
temp = arr[low];
while (i<j) {
//先看右边,依次往左递减
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左边,依次往右递增
while (temp>=arr[i]&&i<j) {
i++;
}
//如果满足条件则交换
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, low, j-1);
//递归调用右半数组
quickSort(arr, j+1, high);
}
public static void main(String[] args){
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
quickSort(arr, 0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
计数排序
计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。(没有重复的数字,如果有重复的数字要进行优化)Ο(n+k)(其中k是整数的范围)快于任何比较排序算法,这是一种牺牲空间换取时间的做法。
//针对c数组的大小,优化过的计数排序
publicclassCountSort{
publicstaticvoidmain(String[]args){
//排序的数组
int a[]={100,93,97,92,96,99,92,89,93,97,90,94,92,95};
int b[]=countSort(a);
for(inti:b){
System.out.print(i+"");
}
System.out.println();
}
public static int[] countSort(int[]a){
int b[] = new int[a.length];
int max = a[0],min = a[0];
for(int i:a){
if(i>max){
max=i;
}
if(i<min){
min=i;
}
}//这里k的大小是要排序的数组中,元素大小的极值差+1
int k=max-min+1;
int c[]=new int[k];
for(int i=0;i<a.length;++i){
c[a[i]-min]+=1;//优化过的地方,减小了数组c的大小
}
for(int i=1;i<c.length;++i){
c[i]=c[i]+c[i-1];
}
for(int i=a.length-1;i>=0;--i){
b[--c[a[i]-min]]=a[i];//按存取的方式取出c的元素
}
return b;
}
}
桶排序
先确定桶的范围,然后将数据分配到桶中。再对桶中的数据进行排序。
public static void bucketSort(int[] arr){
// 计算最大值与最小值
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for(int i = 0; i < arr.length; i++){
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
// 计算桶的数量
int bucketNum = (max - min) / arr.length + 1;
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
for(int i = 0; i < bucketNum; i++){
bucketArr.add(new ArrayList<Integer>());
}
// 将每个元素放入桶
for(int i = 0; i < arr.length; i++){
int num = (arr[i] - min) / (arr.length);
bucketArr.get(num).add(arr[i]);
}
// 对每个桶进行排序
for(int i = 0; i < bucketArr.size(); i++){
Collections.sort(bucketArr.get(i));
}
// 将桶中的元素赋值到原序列
int index = 0;
for(int i = 0; i < bucketArr.size(); i++){
for(int j = 0; j < bucketArr.get(i).size(); j++){
arr[index++] = bucketArr.get(i).get(j);
}
}
}
选择排序
简单选择排序每一趟选出一个最小的与当前元素置换.空间复杂度O(1).不稳定排序。
堆排序:树形排序.空间复杂度O(1).时间复杂度O(nlog2n)
归并排序
增大归并路数,减少归并次数。但是要注意缓存位置够不够用。是一种稳定的排序算法。空间复杂度O(n).时间复杂度O(nlog2n)
public class Main {
public static void main(String[] args) {
int[] arr = {11,44,23,67,88,65,34,48,9,12};
int[] tmp = new int[arr.length]; //新建一个临时数组存放
mergeSort(arr,0,arr.length-1,tmp);
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
}
public static void merge(int[] arr,int low,int mid,int high,int[] tmp){
int i = 0;
int j = low,k = mid+1; //左边序列和右边序列起始索引
while(j <= mid && k <= high){
if(arr[j] < arr[k]){
tmp[i++] = arr[j++];
}else{
tmp[i++] = arr[k++];
}
}
//若左边序列还有剩余,则将其全部拷贝进tmp[]中
while(j <= mid){
tmp[i++] = arr[j++];
}
while(k <= high){
tmp[i++] = arr[k++];
}
for(int t=0;t<i;t++){
arr[low+t] = tmp[t];
}
}
public static void mergeSort(int[] arr,int low,int high,int[] tmp){
if(low<high){
int mid = (low+high)/2;
mergeSort(arr,low,mid,tmp); //对左边序列进行归并排序
mergeSort(arr,mid+1,high,tmp); //对右边序列进行归并排序
merge(arr,low,mid,high,tmp); //合并两个有序序列
}
}
}
基数排序
按位排序,是稳定的。经过几次排序得到最终排序的数据。效率要高于其他稳定性排序。
每次只对数据的某以数位进行排序。
public class RadixSort
{
public static void sort(int[] number, int d) //d表示最大的数有多少位
{
intk = 0;
intn = 1;
intm = 1; //控制键值排序依据在哪一位
int[][]temp = newint[10][number.length]; //数组的第一维表示可能的余数0-9
int[]order = newint[10]; //数组order[i]用来表示该位是i的数的个数
while(m <= d)
{
for(inti = 0; i < number.length; i++)
{
intlsd = ((number[i] / n) % 10);
temp[lsd][order[lsd]] = number[i];
order[lsd]++;
}
for(inti = 0; i < 10; i++)
{
if(order[i] != 0)
for(intj = 0; j < order[i]; j++)
{
number[k] = temp[i][j];
k++;
}
order[i] = 0;
}
n *= 10;
k = 0;
m++;
}
}
public static void main(String[] args)
{
int[]data =
{73, 22, 93, 43, 55, 14, 28, 65, 39, 81, 33, 100};
RadixSort.sort(data, 3);
for(inti = 0; i < data.length; i++)
{
System.out.print(data[i] + "");
}
}
}
外部排序
通常只考虑外部磁盘的读取次数,IO次数。因为这远远大于内存的运算的时间。
核心思想是增大归并的路数,可以减少归并的次数,从而减少IO次数。
多路平衡归并与败者树
使用多路归并,增大归并的路数,会引起内部归并效率降低,如何让内部归并不受影响呢?
引入了败者树。败者树是完全二叉树,叶子结点存放数据,中间结点只存放失败者的段号,通过败者树,拿到最小的值。这个时候内部归并时间不受归并路数增大的影响。
置换-选择排序(生成初始归并段)
如果初始归并段有序,那么将提高排序速度。同时也能减少归并段的个数,不是简单的做定长的除法。
1.开始从输入区读w个数据到工作区,从工作区中找出大于输出区max(初始为0)的最小数据a;
2.将这个最小数据a输出,更新输出区的max数据为a,同时从输入区再读新数据进工作区;
3.如此反复,出现不能输出的情况;就输出#,代表生成了一个归并段。直至把输入区数据消耗完为止。
最佳归并树(找到最佳的归并顺序)
文件经过了置换选择排序后,得到了大小不一的归并段,那么如何找到最佳的归并方式呢?
归并树的代权路径长度WPL即为总读记录数
通过增加长度为0的虚段,遵守哈夫曼树原则,构成一个严格的m叉树。
综上,通过置换选择算法 ,我们得到了最小的归并段树,然后通过最佳归并树知道该如何进行归并。
对N个整数进行排序
开一个数组,然后遍历N个整数,将整数值对应的数组坐标下存储的数据+1,再遍历一遍数组,数组存储的数据是多少,就输出多少个数组坐标对应的数字。(不稳定排序,有负数可以加偏移量,时间复杂度O(n));
如何选择排序算法
1.数据量小就直接插入排序,或者简单选择排序。如果数据本身信息量很大,就用简单选择排序,因为直接插入排序移动数据次数较多。
2.如果数据本身基本有序,那么使用直接插入排序或者冒泡排序比较好。
3.如果数据量很大,就应该使用快速排序,堆排序,归并排序。这其中稳定的就是归并排序,实现简单的就是快速排序,堆排序空间占用比快速排序小。
4.数据量特别大,但是容易分解关键字并且关键字位数少,可以使用基数排序;
5.记录本身特别大的时候,建议使用链表来作为存储结构,较少移动的消耗。