数据结构面试整理(2)

4. Hash表的hash函数,冲突解决方法有哪些

Hash表的基本思想:首先在元素的关键字k和元素的存储位置p之间建立一个对应关系f,使得p=f(k),f称为哈希函数。创建哈希表时,把关键字为k的元素直接存入地址为f(k)的单元;以后当查找关键字为k的元素时,再利用哈希函数计算出该元素的存储位置p=f(k),从而达到按关键字直接存取元素的目的。
散列函数
1) 直接地址
取关键字或关键字的某个线性函数值为哈希地址:H(key)=key 或 H(key)=a·key+b,其中a和b为常数,这种哈希函数叫做自身函数。
2)乘法
该方法包括两个步骤:首先用关键字key乘上某个常数 A(0<A<1) ,并抽取出key.A的小数部分;然后用m乘以该小数后取整。即:
H(key)=mkeyAkeyA
该方法最大的优点是m的选取比除余法要求更低。
3)除法
取关键字被数p除后所得余数为哈希址: H(key)=keyMODp ,不太常用,因为除法的效率比乘法低,乘数相乘法更合适。
4)分段叠加法
将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址,这方法称为折叠法(folding)。
5)伪随机
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 H(key)=random(key) ,其中random为随机函数。通常,当关键字长度不等时采用此法构造哈希函数较恰当。
6)查表法
有一个关键字表,通过关键字可以在该表查询hash值
7)平方取中法
取关键字平方后的中间几位为哈希地址。
解决冲突
1)开放地址法
a)线性探测法
插入元素时,如果发生冲突,算法会简单的从该槽位置向后循环遍历hash表,直到找到表中的下一个空槽,并将该元素放入该槽中,这会使得相同hash值的元素紧挨在一起导致其他hash值的槽被占用。查找元素时,首先散列值所指向的槽,如果没有找到匹配,则继续从该槽遍历hash表,直到:(1)找到相应的元素;(2)找到一个空槽,指示查找的元素不存在,(所以不能随便删除元素);(3)整个hash表遍历完毕(指示该元素不存在并且hash表是满的)
缺点
处理溢出需另编程序。一般可另外设立一个溢出表,专门用来存放上述哈希表中放不下的记录。此溢出表最简单的结构是顺序表,查找方法可用顺序查找。
删除工作非常困难。如果将此元素删除,查找的时会发现空槽,则会认为要找的元素不存在。只能标上已被删除的标记,否则,将会影响以后的查找。
容易产生堆聚现象。所谓堆聚现象,就是存入哈希表的记录在表中连成一片。按照线性探测法处理冲突,如果生成哈希地址的连续序列愈长 ( 即不同关键字值的哈希地址相邻在一起愈长 ) ,则当新的记录加入该表时,与这个序列发生冲突的可能性愈大。因此,哈希地址的较长连续序列比较短连续序列生长得快,这就意味着,一旦出现堆聚 ( 伴随着冲突 ) ,就将引起进一步的堆聚。
b)线性补偿探测法
基本思想是:将线性探测的步长从1改为Q,即将上述算法中的 hash(hash1)%m 改为: hash(hashQ)%m=hash%m+Q%m ,而且要求 Q 与 m 是互质的,以便能探测到哈希表中的所有单元。
c)伪随机探测
基本思想是:将线性探测的步长从常数改为随机数,即令: hash(hashRN)%m ,其中 RN 是一个随机数。在实际程序中应预先用随机数发生器产生一个随机序列,将此序列作为依次探测的步长。这样就能使不同的关键字具有不同的探测次序,从而可以避 免或减少堆聚。基于与线性探测法相同的理由,在线性补偿探测法和随机探测法中,删除一个记录后也要打上删除标记。
2)拉链法
基本思想:将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于 1,但一般均取α≤1。
优点
①拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短
②由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
缺点
指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

5. 各种排序:冒泡、选择、插入、希尔、归并、快排、堆排、桶排、基数的原理、平均时间复杂度、最坏时间复杂度、空间复杂度、是否稳定

排序方式最坏时间复杂度空间复杂度稳定性
冒泡排序O(N^2)O(1)稳定
选择排序O(N^2)O(1)不稳定
插入排序O(N^2)O(1)稳定
希尔排序O(N^2)O(1)不稳定
归并排序O(N*lgN)O(N)稳定
快速排序O(N^2)O(lgN)不稳定
堆排序O(N*lgN)O(1)不稳定
桶排序O()O()O()
计数排序O()O()O()

5.1冒泡法

/**
 * 冒泡排序
 * 思路:内部循环每走一趟排好一位,依次向后排序
 */
private static void bubbleSort(int[] data) {
    int temp;
    for (int i = 0; i < data.length; i++) {//每一次确定第i小的数,第i位数据与i之后的数据
        for (int j = i+1; j < data.length; j++) {
            if (data[i]>data[j]) {
                temp =data[i];
                data[i]=data[j];
                data[j] = temp;
            }
        }
    }
}

5.2 选择法

/**
 * 选择排序
 * 思路:每次循环得到最小值的下标,然后交换数据。
 * 如果交换的位置不等于原来的位置,则不交换。
 */
public static void selectSort(int[] data){
    int index=0;
    for (int i = 0; i < data.length; i++) {
        index = i;//保存最小值下标
        for (int j = i; j < data.length; j++) {
            if (data[index]>data[j]) {
                index = j;
            }
        }
        if (index != i) {
            swap(data,index,i);
        }
    }
}

5.3 插入排序

 /**
     * 插入排序
     * 思路:将数据插入到已排序的数组中。
     */
    public static void InsertSort(int[] data) {
        int temp;
        for (int i = 1; i < data.length; i++) {
            temp = data[i];//保存待插入的数值
            int j = i;
            for (; j>0 && temp<data[j-1]; j--) {
                data[j] = data[j-1];
                //如果带插入的数值前面的元素比该值大,就向后移动一位
            }
            //内部循环结束,找到插入的位置赋值即可。
            data[j]=temp;
        }
    }

5.4 希尔排序

参考:白话经典算法系列之三 希尔排序的实现
希尔排序的实质就是分组插入排序,该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。基本思想:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的。

/**
 * 希尔排序(缩减增量排序)
 * 想想也不难。
 * 思路:三层循环
 * 第一层循环:控制增量-增量随着程序的进行依次递减一半
 * 第二层循环:遍历数组
 * 第三层循环:比较元素,交换元素。
 * 这里需要注意的是:比较的两个元素和交换的两个元素是不同的。
 */
public static void shellSort(int[] data) {
    int k;
    for (int div = data.length/2; div>0; div/=2) {
        for (int j = div; j < data.length; j++) {
            int temp = data[j];
            for (k=j; k>=div && temp<data[k-div] ; k-=div) {
                data[k] = data[k-div];
            }
            data[k] = temp;
        }
    }
}

5.5 归并排序

基本思路:将两个已经排好序的数组插入到第三个数组当中。分割并排序

/**
 *data 待排序数组
 *temp 存储排序数组
 *left 左边数组起始位置
 *right 右边数组起始位置
 *center 左右数组断点位置
 */
public static void mergeSort(int[] data,int[] temp,int left,int center,int right){
    int leftEnd = center;
    int rightStar = center+1;
    int len = right-left+1;
    int tempPos = left;//排序数组下标
    //将两个已经排序的数组进行比较,将元素添加到temp数组中保存。
    while (left<=leftEnd&&rightStar<=right) {
        if (data[left]<=data[rightStar]) {
            temp[tempPos++] = data[left++];
        }else {
            temp[tempPos++] = data[rightStar++];
        }
    }
    //右数组空,左数组未空
    while (left<=leftEnd) {
        temp[tempPos++]=data[left++];
    }
    //左数组空,右数组未空
    while (rightStar<=right) {
        temp[tempPos++]=data[rightStar++];
    }
    //将排序结果拷贝回原来的数组
    for (int i = 0; i < len; i++,right--) {
        data[right]=temp[right];
    }
}

5.6 快速排序

基本思想是:1)先从数列中取出一个数作为基准数;2)分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边;3)再对左右区间重复第二步,直到各区间只有一个数。
参考:白话经典算法系列之六 快速排序 快速搞定

5.7 堆排序

白话经典算法系列之七 堆与堆排序

5.8 桶排序

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值