X32专项练习部分04
算法的时间复杂度与初始序列
/*
前言:
程序的执行效率
主要和CPU速度,程序的循环逻辑和选择逻辑的关系,程序的数据结构,数据量的使用
换句话说
与数据的存储结构、数据的逻辑结构、程序的控制结构、所处理的数据量等有关
在下列排序算法中,哪一个算法的时间复杂度与初始序列无关
直接插入排序
冒泡排序
快速排序
直接选择排序
1.直接插入排序
就是从后往前扫描
需要和前面都有序序列对比插入的位置,如果本身有序,则每次对比一个即可
最坏情况,则需要一直对比到第一个元素
最好时间复杂度O(n),最坏时间复杂度O(n^2)
2.冒泡排序
这里可以拿出优化后的冒泡排序
有一个标志位,如果是已经排好序,那么一进入循环就推出了
时间复杂度为O(1)
也可以加一层判断,如果初始数组有序的话,若没发生元素交换
则认为元素已经有序,算法最好时间复杂度O(n)
最好时间复杂度为O(n),最坏时间复杂度为O(n^2)
3.快速排序
快速排序是选择一个元素,将比它小的放在它的左边,比它大的放右边,
递归完所有未完成排序的数组即可完成最后的目标
当分区选取的基准元素为待排序元素中的最大或最小值时,为最坏的情况O(n^2),
当分区选取的基准元素为待排序元素中的"中值"
为最好的情况,时间复杂度为O(nlog2的n次方)。
也有这么一种说法,完全有序的数组会遇到最坏的情况,也就是O(n^2)
完全反序的数组会遇到最好的情况,也就是O(nlog2的n次方)
4.直接选择排序
D选项,直接选择排序是每一次从待排序的数据元素中选出最小(或最大)的一个元素
存放在序列的起始位置,直到全部待排序的数据元素排完
不管序列怎么样,每次都要遍历寻找最大的值,必须遍历完全,不然无法判断是否最大
因为选出待排序数组的最大值或最小值需要扫描全部的元素所以和元素初始序列无关
时间复杂度都是O(n^2)
有一张专门的图对此进行了说明
https://www.nowcoder.com/questionTerminal/c77fa23d7af4429c872bed13688e5cfc
时间复杂度考虑到了比较次数和移动次数
比如直接插入排序
比较次数始终为n-1
但如果数组初始序列有序
那么不需要移动
最好的时间复杂度为O(n)
*/
斐波拉契数列的时空复杂度
/*
以下函数的时间复杂度和空间复杂度为( )
int Function(int n)
{
if (n <=1)
return n;
else
return Function(n-1)+ Function(n-2);
}
T(n)=O(2^n),S(n)=O(n)
T(n)=O(n),S(n)=O(n)
T(n)=O(n),S(n)=O(1)
T(n)=O(2^n),S(n)=O(1)
正确答案:A
这道题就是求斐波拉契数列
通过画递归树
根据基础知识得知
高度为k的二叉树最多有2^k - 1个叶子节点
也就是递归过程中函数的调用次数
二叉树的高度为n-1
所以空间复杂度S(n) = O(n)
*/
单链表复杂度操作
/*
有一个单向链表,头指针和尾指针分别为p,q,以下哪项操作的复杂度不受队列长度的影响?
正确答案: ACD
删除头部元素
删除尾部元素
头部元素之前插入一个元素
尾部元素之后插入一个元素
由于是单链表,不能直接由尾部节点得到前一个节点
所以还是要遍历一遍才能知道尾指针前一节点,所以与队列长度有关
B错误
A:修改头指针的指针域,跳过头部元素
B:需要修改尾部元素(尾指针指向结点)的前驱结点指针域,遍历后得到,与长度相关
C:修改头指针和插入元素的指针域
D:修改尾指针指向结点(尾部元素)的指针域
*/
随机访问和顺序访问
/*
双向链表可随机访问任一结点,这样的说法正确吗?
正确
不正确
顺序表:随机访问
链表:顺序访问
就是数组和链表的差别
数组查询效率高,随机增删效率低,链表相反
*/
获取任意指定值
/*
以下那种结构,平均来讲获取任意一个指定值最快?()
二叉排序树
队列
栈
哈希表
正确答案:D
哈希表无论查找,增加还是删除,时间复杂度都是O(1)
对于栈和队列来说,查找就意味着把元素挨个出栈或者出队,故平均时间复杂度是O(n);
而哈希表,直接通过关键码key查找元素,平均为O(1);
*/
单链表节点的数据元素
/*
单链表结点的数据元素只能是哪一种?( )。
正确答案: C
整型
字符串
任何数据类型
实型
单链表定义:单链表是一种链式存取的数据结构,用一组 地址 任意存储单元存放线性表中的数据元素
链表中的数据是以结点来表示的
每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置)
元素就是存储数据的存储单元,指针就是连接每个结点的地址数据
数据元素:是数据的基本单位,数据元素也叫做结点或记录
在计算机中中通常作为一个整体进行考虑和处理
有时,一个数据元素可由若干个数据项组成
例如,一本书的书目信息为一个数据元素,而书目信息的每一项(如书名、作者名等)为一个数据项
数据项是数据的不可分割的最小单位
所以单链表只是把元素连起来
并不考虑元素类型
*/
双向链表插入节点操作
/*
对于双向循环链表,在p指针所指的结点之后插入s指针所指结点的操作应为()
正确答案:D
p->right=s;s->left=p;p->right->left=s;s->right=p->right;
p->right=s;p->right->left=s;s->left=p;s->right=p->right;
s->left=p;s->right=p->right;p->right=s;p->right->left=s;
s->left=p;s->right=p->right;p->right->left=s;p->right=s;
双向链表的插入顺序:
先搞定插入节点的前驱和后继
再搞定后结点的前驱
最后搞定前结点的后继
一定要记住
*/
顺序表的适用场景
/*
若某线性表最常用的操作
是存取任一指定序号的元素和
在最后进行插入和删除运算
则利用()存储方式最节省时间。
正确答案:A
顺序表
双链表
带头结点的双循环链表
单循环链表
“存取任一指定序号”最好的方法是实现“随机存取”,则可采用顺序表
并且,因为插入和删除操作都是在最后进行的
所以无需大量移动数据元素,选项A是最合适的
拓展:
随机存取和顺序存取比较
比如给你一列纵队的人,让你找到位置为5的人
第一种方法是你从第一个位置开始一个一个往后找,直到找到位置为5的位置,这样的就是顺序存取
而如果你已经知道5的位置
不需要经过前面的四个位置直接到达位置为5的位置,那么这样的就是随机存取
换句话说,随机存取方便遍历查找
*/
多线程不安全时可能得到的结果
/*
假设 a 是一个由线程 1 和线程 2 共享的初始值为 0 的全局变量
则线程 1 和线程 2 同时执行下面的代码,最终 a 的结果不可能是()
boolean isOdd = false;
for(int i=1;i<=2;++i){
if(i % 2 == 1){
isOdd = true;
}else{
isOdd = false;
}
a += i*(isOdd ? 1 : -1); // a累加的结果
}
-1 -2 0 1
正确答案:D
说明:
情况1:
线程1执行完后执行线程2
1-2+1-2 = -2
情况2:
线程1线程2同时得到a += 1的结果
第2层for循环时线程12分别得到-2
1-2-2 = -3
情况3:
与情况2相反
分别得到1
同时得到-2
1+1-2 = 0
情况4:
既同时得到1
又同时得到2
1-2 = -1
*/
结果集ResultSet
/*
ResultSet中记录行的第一列索引为?
-1
0
1
以上都不是
正确答案:C
结果集ResultSet索引从1开始
拓展:结果集
表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
ResultSet接口提供用于从当前行获取列值的获取方法getBoolean、getLong等
ResultSet的索引index可以认为是数据库的第几行,实体数据是从第二行开始,第一行是属性行
所以通常调用next方法跳过属性行
*/