牛客网:C++面试宝典——算法与数据结构(3)数组 (4)排序 (5)哈希

牛客网链接

● 请你回答一下Array&List, 数组和链表的区别

区别数组链表
内存在内存中,数组是一块连续的区域。在内存中可以存在任何地方,不要求连续。
 数组需要预留空间,在使用前要先申请占内存的大小,可能会浪费内存空间。不指定大小
插入、删除插入数据和删除数据效率低,插入数据时,这个位置后面的数据在内存中都要向后移。删除数据时,这个数据后面的数据都要往前移动。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。
随机访问因为数组是连续的,知道每一个数据的内存地址,可以直接找到给地址的数据。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。
扩展并且不利于扩展,数组定义的空间不够时要重新定义数组。不指定大小,扩展方便。链表大小不用定义,数据随意增删。
优点支持随机访问,查找速度快

插入删除速度快

内存利用率高,不会浪费内存

大小没有固定,拓展很灵活。

缺点

插入和删除效率低

可能浪费内存

内存空间要求高,必须有足够的连续内存空间

数组大小固定,不能动态拓展

不能随机查找,必须从第一个开始遍历,查找效率低
场景如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。经常插入删除的话

● 一个长度为N的整形数组,数组中每个元素的取值范围是[0,n-1],判断该数组否有重复的数,请说一下你的思路并手写代码

返回true——有重复的

bool func(int *array,int n){
	if(array==NULL)	return false;
	int i,temp;
	for(i=0;i<n;i++){
		if(arr[i]!=i){
			if(array[array[i]]==array[i])	return true;
			temp=array[i];
			array[i]=array[array[i]];
			array[array[i]]=temp;			
		}
	}
	return false;
}//返回true表示有重复的 

快排

int func(vector<int> &num,int l,int r){
	int pivot=num[l];
	while(l<r){
		while(l<r&&num[r]>=pivot)	r--;
		if(l<r)	num[l]=num[r];
		while(l<r&&num[l]<=pivot)	l++;
		if(l<r) num[r]=num[l];
	}
	num[l]=pivot;
	return l;
}
void quicksort(vector<int>&num,int l,int r){
	if(l<r){
		int mid=func(num,l,r);
		quicksort(num,l,mid-1);
		quicksort(num,mid+1,r);
	}
}

其实不需要排序的,只需要根据mid与k的大小关系就行调整就行

● 请问求第k大的数的方法以及各自的复杂度是怎样的,另外追问一下,当有相同元素时,还可以使用什么不同的方法求第k大的元素

首先使用快速排序算法将数组按照从大到小排序,然后取第k个,其时间复杂度最快为O(nlogn)

(1)堆排序:

大根堆,首尾交换+元素下沉 参考 参考

#include<stdio.h>
void swap(int *a,int *b){
	int temp=*a;
	*a=*b;
	*b=temp; 
} 
void adjust(int *arr,int i,int n){
	int j=2*i+1;
	while(j<n){
		if(arr[j+1]>arr[j]&&j+1<n)	j++;
		if(arr[i]>=arr[j])	return;
		swap(&arr[i],&arr[j]);
		i=j;
		j=2*i+1;
	}
}
void makeheap(int *arr,int n){
	int i=n/2-1;
	for(;i>=0;i--){
		adjust(arr,i,n);
	}
}
void heapsort(int *arr,int n){
	int i=0;
	makeheap(arr,n);
	for(i=n-1;i>=0;i--){
		swap(&arr[i],&arr[0]);
		adjust(arr,0,i);
	}
}
int main(){
	int arr[9]={12,3,435,23,1,32,6,7,7};
	int n=9;
	heapsort(arr,n);
	for(int i=0;i<9;i++)	printf("%d\n",arr[i]);
	return 0;
}

求第k大的话,只需要排序到第k大的数即可,时间复杂度O(n+klogn)

代码修改为:

void heapsort(int *arr,int n,int k){
	int i=0;
	makeheap(arr,n);
	for(i=n-1;i>=n-k;i--){
		swap(&arr[i],&arr[0]);
		adjust(arr,0,i);
	}
}
int main(){
	int a[6]={12,32,4,11,67,7};
	int n=6;
	int k=3;
	heapsort(a,n,k);
	printf("%d\n",a[n-k]);
	return 0;
}

map:时间复杂度,map查找的时间复杂度O(lgn)

int func(int* a, int n, int k) {
	map<int, int> m;
	for (int i = 0; i < n; i++) {
		if (m.count(a[i]))	m[a[i]]++;
		else
			m[a[i]] = 1;
	}
	map<int, int>::iterator it=m.end();
	it--;
	int count = 0;
	for (; it != m.begin(); it--) {
		count += it->second;
		if (count >= k)	return it->first;
	}
	it--;
	return it->first;
}
int main() {
	int a[10] = { 12,43,65,7,2,3,54,76,2,100 };//2,2,3,7,12,43,54,65,67,100
	int n = 10;
	int k = 3;
	int m=func(a, n, k);
	cout << m << endl;	
	return 0;
}

● 请你来介绍一下各种排序算法及时间复杂度

排序时复(平均)时复(最坏)时复(最好)空间复杂度稳定
直接插入排序O(n^2)O(n^2)O(n^2)O(1)稳定
堆排序O(nlgn)O(nlgn)O(nlgn)O(1)不稳定
快速排序O(nlgn)O(n^2)O(nlgn)O(nlgn)不稳定
归并排序O(nlgn)O(nlgn)O(nlgn)O(n)稳定
冒泡排序O(n^2)O(n^2)O(n)O(1)稳定
选择排序(每次排序最小的元素)O(n^2)O(n^2)O(n^2)O(1)稳定

计数排序:适用于一定范围的整数排序。在取值范围不是很大的情况下,它的性能在某些情况甚至快过那些O(nlogn)的排序,例如快速排序、归并排序

● 请问海量数据如何去取最大的k个

1.最小堆法

这是一种局部淘汰法。先读取前K个数,建立一个最小堆。然后将剩余的所有数字依次与最小堆的堆顶进行比较,如果小于或等于堆顶数据,则继续比较下一个;否则,删除堆顶元素,并将新数据插入堆中,重新调整最小堆。当遍历完全部数据后,最小堆中的数据即为最大的K个数。

4.分治法

将全部数据分成N份,前提是每份的数据都可以读到内存中进行处理,找到每份数据中最大的K个数。此时剩下N*K个数据,如果内存不能容纳N*K个数据,则再继续分治处理,分成M份,找出每份数据中最大的K个数,如果M*K个数仍然不能读到内存中,则继续分治处理。直到剩余的数可以读入内存中,那么可以对这些数使用快速排序的变形或者归并排序进行处理。

稳定的排序

直接插入排序,冒泡排序,基数排序,归并排序

 

第6章 第5节 哈希

 

● 请你来说一说hash表的实现,包括STL中的哈希桶长度常数

●构造哈希的方法:

直接地址法:数据元素关键字k本身或它的线性函数作为它的哈希地址

平方取中法:这个方法是先取关键字的平方,然后根据可使用空间的大小,选取平方数是中间几位为哈希地址。

除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key % p(p≤m)

● 请你说一说哈希冲突的解决方法

1、开放定址

开放地址法有个非常关键的特征,就是所有输入的元素全部存放在哈希表里,也就是说,位桶的实现是不需要任何的链表来实现的,换句话说,也就是这个哈希表的装载因子不会超过1。它的实现是在插入一个元素的时候,先通过哈希函数进行判断,若是发生哈希冲突,就以当前地址为基准,根据再寻址的方法(探查序列),去寻找下一个地址,若发生冲突再去寻找,直至找到一个为空的地址为止。所以这种方法又称为再散列法。

①线性探查

dii=1,2,3,…,m-1;这种方法的特点是:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。

②二次探查

di=12,-12,22,-22,…,k2,-k2    ( k<=m/2 );这种方法的特点是:冲突发生时,在表的左右进行跳跃式探测,比较灵活。

2、链地址

将产生冲突的值以链表的形式连起来

将所有哈希值相同的Key通过链表存储。key按顺序插入到链表中

适合无法确定表长的情况,但是会增加空间消耗(指针需要空间)

3、公共溢出区

建立一个公共溢出区域,把hash冲突的元素都放在该溢出区里。查找时,如果发现hash表中对应桶里存在其他元素,还需要在公共溢出区里再次进行查找。

将哈希表分为基本表和溢出表,凡是与基本表发生冲突的元素都填入到溢出表中

4.再哈希法

一次准备构造多个哈希函数,发生冲突时使用其他的哈希函数,直到不再产出冲突

这种方法增加了计算时间(每次冲突都要重新散列,计算时间增加。)

 

● 请你说一下哈希表的桶个数为什么是质数,合数有何不妥?

哈希表的桶个数使用质数,可以最大程度减少冲突概率,使哈希后的数据分布的更加均匀。如果使用合数,可能会造成很多数据分布会集中在某些点上,从而影响哈希表效率。

比如:1,3,5,7,9,11,13,15   如果取除以4的余数则都分布在1,3

关键字都分布差值为桶的因子2的的地址上

下面我们假设,p=tq,t为常数,q为某个质数,关键词的差值是q的倍数,那么h(x)-h(y)=q*n,

 哈希地址 (h(x)-h(y))%p= q*n%(t*q)=(q*n-t*q*r)=q*(n-tr);  t位整除的值。

因此,均散列在差值为q的地址上。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值