选择题部分
选择题部分考得比较基础,主要是排序算法相关(算法描述、时间复杂度和空间复杂度相关)和基本的数据结构(队列、栈、还有二叉树)。
1.排序算法回顾
首先是各个算法的时间复杂度总结(根据算法本身的复杂程度排序)---- 这个是需要背下来的:
算法名称 | 平均时间 | 最好时间 | 最坏时间 |
---|---|---|---|
冒泡排序(稳定) | O(n^2) | O(n) | O(n^2) |
选择排序(不稳定) | O(n^2) | O(n^2) | O(n^2) |
直接插入排序(稳定) | O(n^2) | O(n) | O(n^2) |
希尔排序(稳定) | O(nlog2n) | O(n) | O(n^2) |
归并排序(稳定) | O(nlogn) | O(nlogn) | O(nlogn) |
快速排序(稳定) | O(nlogn) | O(nlogn) | O(n^2) |
基数排序(稳定) | O(n) | O(n) | O(n) |
堆排序(不稳定) | O(n) | O(n) | O(n) |
桶排序(不稳定) | O(n) | O(n) | O(n) |
链接: 排序算法稳定性讲解.
1.Bubble Sort(冒泡排序)
算法介绍:
遍历需要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
2.Selection Sort(选择排序)
无论什么数据进去都是O(n^2)的时间复杂度。一般建议数据规模越小越好。
唯一的好处是不占用额外的内存空间。
算法介绍:
遍历需要排序的数列,在未排序的数列中找到最小(最大)的元素放到该位置。
3.Insertion Sort(插入排序)
算法介绍:
待排序数列可以分为两个部分【已排序、待排序】
遍历需要排序的数列,当前元素需要和已排序序列从后往前依次比较,插入到已排序数列的适当位置。
4.Shell Sort(希尔排序)
我认为希尔排序其实就是一种进阶版的插入排序,其改进点就是根据增量分组,再对分组进行直接插入排序。
新的增量一般是取上次增量的一半,即 length /= 2 直到为 1。
# Shell sort
def shellSort(nums):
gap = len(nums) // 2
while gap > 0:
for i in range(gap, len(nums)):
j = i
while j >= gap:
if nums[j] < nums[j-gap]:
nums[j], nums[j-gap] = nums[j-gap], nums[j]
else:
break
j -= gap
gap /= 2
return nums
5.Merge Sort(归并排序)
归并排序主要就是采用了分而治之的思想。
从最小的两两比较再到最大的两两比较。
# Merge sort
# 采用的是从上到下的递归方法
def mergeSort(nums):
if len(nums) < 2:
return nums
mid = len(nums) // 2
left, right = nums[:mid], nums[mid:]
return merge(mergeSort(left), mergesort(right))
# 我很喜欢递归调用,看着很舒服!
def merge(left, right):
res = list()
while left and right:
if left[0] <= right[0]:
res.append(left.pop(0))
else:
res.append(right.pop(0))
while left:
res.append(left.pop(0))
while right:
res.append(right.pop(0))
return res
6.Quick Sort(快速排序)
快排主要是设定了一个基准值(一般是该序列第一个值),和所在序列中所有元素进行比较,小的放到前面,大的放到后面。
然后再分别对前面的序列和后面的序列设定基准值,再按照上述步骤进行操作。
# Quick Sort
def quickSort(nums):
if len(nums) < 2:
return nums
else:
pivot = nums[0]
greater = [i for i in nums[1:] if i > pivot]
less = [i for i in nums[1:] if i <= pivot]
return quickSort(less) + [pivot] + quickSort(grearer)
7.Heap Sort(堆排序)
当元素个数较少时,堆排序的大部分时间花在了堆的初始化和向下筛选上,当元素较多时,具有较好的效率。
# Heap Sort
def heapify(nums, n, i):
largest = i
left = 2 * i + 1
right = 2 * i + 2
if left < n and nums[largest] < nums[left]:
largest = left
if right < n and nums[largest] < nums[right]:
largest = right
if largest != i:
nums[i], nums[largest] = nums[largest], nums[i]
heapify(nums, n, largest)
def heapSort(nums):
n = len(nums)
# 构建大堆
for i in range(n, -1, -1):
heapify(nums, n, i)
# 一个个交换元素
for i in range(n - 1, 0, -1):
nums[i], nums[0] = nums[0], nums[i]
heapify(nums, i, 0)
return nums
7.Counting Sort(计数排序)
顾名思义将输入的数据值转化为键存储在额外开辟的数组空间中,作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
比如有无序序列15136321
那么最小是1,最大是6。可以构建一个长度为6的数组 Arr,Arr [i] 的值为“i+1”出现的次数作为value。
那么就有:
1 、2、 3、 4、 5、 6
对应Arr
312011
根据这个Arr进行输出就可以了。
8.Bucket Sort(桶排序)
桶排序的思想也非常简单,将元素分到几个不同的桶中,再对每个桶中的元素进行排序。结果一次输出就是有序的了。
图解如下:
# Bucket Sort
def bucketSort(nums, bucket_size):
minValue = min(nums)
maxValue = max(nums)
res = list()
bucketCount = (maxValue - minValue + 1) // bucket_size
bucket_lists = [[] for i in range(bucket_size)]
for i in nums:
bucket_index = (i-minValue) // bucketCount
bucket_lists[bucket_index].append(i)
for i in range(len(bucket_lists)):
# 这里随便用个什么排序的方法都可以
bucket_lists[i] = Sort(bucket_lists[i])
for i in bucket_lists:
if len(i) != 0:
res.extend(i)
return res
8.Radix Sort(基数排序)
将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从个位开始依次进行排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
2.二叉树相关
二叉树在笔试中也经常出现,比如二叉树搜索等。。。
2.1基本知识点
度:结点下面连接的结点数称为度,树的度是整个树中结点的度是最大的那个度。
根节点: 根结点是树最顶端的结点(根结点只有一个)
子结点: 除了根结点外,本身下面还连接有结点的结点
叶子结点: 本身下面不再有结点的结点(即末端),又称为终端结点。
二叉树的计算性质
- 二叉树的第i层上最多有2(i-1)个结点
- 深度为k的二叉树最多有2k-1个结点
- 对于任何一棵树,如果其终端结点的结点数为n0,度为2的节点数为n2,那么n0 = n2 + 1
- 具有n个结点的完全二叉树的深度为[(log_2)n]+1,其中[x]表示不大于x的最大整数
- 对有n个结点的完全二叉树,按层序遍历编号,对于第i个结点,其左子树是2i,如2i>n则不存在左子树,是叶子结点。右子树是2i+1,如2i+1>n则不存在右子树。
- 计算叶子节点数量的例题
一棵树度为4,其中度为1,2,3,4的结点个数分别为4,2,1,1,则这棵树的叶子节点个数为多少?
在任意一棵树中, 结点总数 = 度数 * 该度数对应的结点数 + 1。
结点总数 = 1 * 4 + 2 * 2 + 3 * 1 + 4 * 1 + 1 = 16。
叶子结点总数 = 16 - 4 - 2 - 1 - 1 (总结点数 - 度不为0的个数)= 8。
则 n0 = 8。
二叉树的遍历
二叉树由三个基本单元组成:根结点、左子树、右子树。
如果能依次遍历这三个部分,便是遍历了整个二叉树。因此衍生出两种遍历方式:深度优先遍历、广度优先遍历。
【深度优先遍历】
先序遍历(根左右)
(1)先访问根结点
(2)先序遍历左子树
(3)先序遍历右子树
中序遍历 (左根右)
(1)中序遍历左子树
(2)访问根结点
(3)中序遍历右子树
后序遍历(左右根)
(1)后序遍历左子树
(2)后序访问右子树
(3)访问根结点
- 例题:已知一颗二叉树的中序序列和后序序列分别是BDCEAFHG和DECBHGFA,画出这颗树。
- 例题:已知一颗二叉树的后序序列为DBFEGCA中序序列为DBAFECG,求它的先序序列。
广度优先遍历【层序遍历】
按照层次从上到下,每层从左到右地访问,可以使用队列来实现。
2.1.1 完全二叉树、满二叉树
满二叉树:就是满的,看起来很完美,很顺畅。
只有度为0和2的结点,并且度为0的结点在同一层上,则是满二叉树。
完全二叉树:深度为k,有n个结点的二叉树当且仅当每一个结点斗鱼深度为k的满二叉树中编号1到n的结点编号对应,称为完全二叉树。
2.1.2 二叉查找树、平衡二叉树
二叉查找树
二叉查找树,又被称为二叉搜索树。一句话就是左孩子比父结点小,右孩子比父结点大,还有一个特性就是“中序遍历”可以让结点有序。
在二叉树查找树中
- 若任意结点的左子树不空,则左子树上所有结点的值均小于它根结点的值;
- 任意结点的右子树不空,则右子树上所有结点的值均大于它的根结点;
- 任意结点的左、右子树也分别为二叉查找树;
- 没有键值相等的结点。
平衡二叉树
平衡二叉查找树:简称平衡二叉树。这种左右子树的高度相差不超过 1 的树为平衡二叉树。
平衡二叉树的几个性质
- 可以是空树。
- 加入不是空树,任何一个结点的左子树与右子树都是平衡二叉树,并且高度之差的绝对值不超过1。
- 平衡就如天平,即两边的分量大约相同。
二叉搜索树的时间复杂度&空间复杂度
如果二叉树是平衡的,则n个结点的二叉排序树的高度为 log(n+1) ,其查找效率为O(log(n)),近似于折半查找。
如果二叉排序树完全不平衡,则其深度可达到n,查找效率为O(n),退化为顺序查找。
一般的二叉树的查找性能在O(log n)到O(n)之间。
二叉树的存储要求:需要树形结构,相比顺序存储需要占用更多的空间,但也有链接数据结构灵活可拓展的优点。
二叉树的空间复杂度: 应为需要建立排序二叉树,所以空间复杂度O(n)。
3.队列
先进先出
4.栈
先进后出
填空题部分
- 有个英语翻译题,反正很坑就行了。
- 文件的逻辑结构和物理结构:
逻辑结构:指一个文件在用户面前所呈现的形式。又称文件组织。
逻辑结构两种形式
- 纪录式文件(有结构式文件)
- 字符流式文件(无结构式文件),也称流式文件
物理结构:指文件在外存上的存储组织形式。这不仅和存储介质的存储性能有关,还与所采用的外存分配方式有关。
物理结构各种形式
- 连续文件结构:要求为每个文件分配一组相邻的盘块。
- 串联文件结构:文件的逻辑结构可以通过链接指针体现,逻辑上相邻的位置关系通过物理上的上下块之间的指针来体现相邻的位置关系。
- 索引文件结构:按照文件逻辑结构顺序的排序的盘块号集合,即第一个位置的记录的地址在哪儿,第二个位置的记录的地址在哪儿…。
- 散列文件结构:根据构造的散列函数与处理冲突的方法将一组关键字映射到一个有限的连续地址集合上,并以关键字在该集合中的“象”作为记录的存储位置,按照这种方法组织起来文件称为散列文件或哈希文件 ,或称杂凑文件。
- 数据库中,char和varchar的区别。
- 长度不同:char类型的长度是固定的,varchar类型的长度是可变的。
- 效率不同:char类型每次修改的数据长度相同,效率更高。varchar每次修改的数据长度不同,效率更低。
- 存储不同:char类型存储的时候是初始预计字符串再加上一个记录字符串长度的字节,占用空间较大。varchar类型存储的时候是实际字符串再加上一个记录字符串长度的字节,占用空间较小。
- DHCP和NAT的区别。
- DHCP是动态主机分配协议,它分为服务器端和客户端。所有的IP网络设定数据都由DHCP服务器集中管理,并负责处理客户端的DHCP要求;而客户端则会使用服务器分配下来的IP环境数据。
- NAT是网络地址转换的缩写。NAT和IP伪装是完全一样的概念。由IPF的NAT提供的一项功能是将防火墙后的本地局域网(LAN)共享一个ISP提供的IP地址来接入Internet公网。NAT会自动地将每一台PC在内网的LAN IP地址,在离开防火墙时转换为公网的IP地址。
编程题部分
长度为m的数组随机输出n个数,每个数输出的概率相同。
ps: 虽然题看起来简单,但是在笔试过程中需要自己纯手打各种函数,没有各种提示,所以一些基本的数据函数要背下来。