一起来复习下数据结构吧

栈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.记录本身特别大的时候,建议使用链表来作为存储结构,较少移动的消耗。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值