数据结构算法篇
算法的魅力
使用经典的百鸡百钱问题来说明,一共列举了三种方式,每种方式的循环层数都不一样
long start1 = System.nanoTime();
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 34; j++) {
for (int k = 0; k < 300; k = k + 3) {
if (5 * i + 3 * j + k / 3 == 100 && i + j + k == 100) {
System.out.println(i + "-" + j + "-" + k);
}
}
}
}
long end1 = System.nanoTime();
System.out.println("三层循环耗时:" + (end1 - start1));
long start2 = System.nanoTime();
int k_temp = 0, k = 0;
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 34; j++) {
k_temp = 100 - i - j;
if (k_temp % 3 == 0) {
k = k_temp;
}
if (5 * i + 3 * j + k / 3 == 100 && i + j + k == 100) {
System.out.println(i + "-" + j + "-" + k);
}
}
}
long end2 = System.nanoTime();
System.out.println("两层循环耗时:" + (end2 - start2));
long start3 = System.nanoTime();
int x, y, z, t;
for (t = 0; t <= 3; t++) {
x = 4 * t;
y = 25 - 7 * t;
z = 75 + 3 * t;
if ((5 * x + 3 * y + z / 3) == 100 && (x + y + z) == 100) {
System.out.println(x + "-" + y + "-" + z);
}
}
long end3 = System.nanoTime();
System.out.println("一层循环耗时:" + (end3 - start3));
#输出结果
0-25-75
4-18-78
8-11-81
12-4-84
三层循环耗时:23438800
0-25-75
4-18-78
8-11-81
12-4-84
两层循环耗时:432300
0-25-75
4-18-78
8-11-81
12-4-84
一层循环耗时:280700
结论:很明显,循环嵌套层越少,效率越高
算法概念
时间复杂度
时间复杂度是对一个算法在运行过程中计算耗时的一个量度,也是优化算法的最核心的目标依据。
空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,这个概念在很早以前是需要关注的,但是随着硬件设施的提升和廉价,空间复杂度逐渐变成了边角料,我们只需要在平常的程序设计中尽量注意一些习惯就好:
1、尽量用一维数组,二维数组用结构体一维数组代替
2、尽量少引入中间状态、临时状态,减少状态维护占用
3、数组使用尽量注意小初始化,高扩容频率(这个影响到了性能,慎用)
.....
常见的数据结构
- **栈(Stack):**线性表。类似于弹匣,只能从一个入口压栈、弹栈—先进后出。
- **队列(Queue):**线性表。只允许在表的一端进行插入操作,而在另一端进行删除操作—先进先出。
- **数组(Array):**数组是一种聚合数据类型,它是将具有相同类型的若干变量有序地组织在一起的集合。
- **链表(Linked List):**物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
- **堆(Heap):**一种特殊的树形数据结构,一般讨论的堆都是二叉树。
- **散列表(Hash table):**散列表源自于散列函数(Hash function),其思想是如果在结构中存在关键字和T相等的记录,那么必定在F(T)的存储位置可以找到该记录,这样就可以不用进行比较操作而直接取得所查记录。
排序优化
准备工作
定义一个随机数生成方法:
public static Integer[] generateRandomArray(int len, int max) {
Integer[] arr = new Integer[len];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * max);
}
return arr;
}
测试遍历方法:
public static void main(String[] args) {
int N = 20000;
Integer[] arr = generateRandomArray(N, 100000);
long start = System.currentTimeMillis();
sort(arr);
long end = System.currentTimeMillis();
System.out.println("**排序耗时:" + (end - start));
System.out.println(Arrays.asList(arr));
}
元素位置交换方法:
private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
冒泡排序
依次比较相邻的两个数,将小数放在前面,大数放在后面。
第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。至此第一趟结束,将最大的数放到了最后。
第二趟:仍从第一对数开始比较(因为可能由于第2个数和第3个数的交换,使得第1个数不再小于第2个数),将小数放前,大数放后,一直比较到倒数第二个数(倒数第一的位置上已经是最大的),第二趟结束,在倒数第二的位置上得到一个新的最大数(其实在整个数列中是第二大的数)。
如此下去,重复以上过程,直至最终完成排序。
public static void sort(Integer[] arr) {
int count = 0;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
count++;
if (arr[j + 1] < arr[j]) {
swap(arr, j + 1, j);
}
}
}
System.out.println("循环计算次数:" + count);
}
#输出结果(无论你执行多少次,循环次数都是固定的)
循环计算次数:199990000
冒泡排序耗时:1529
结论:两层循环,性能不太理想
冒泡排序优化
改进方法:如果我们发现在排序过程中,没有发生一次交换,我们可以让它提前结束!
我们在排序过程中设置一个标标记flag判断一下元素是否进行交换
public static void sort(Integer[] arr) {
int count = 0;
boolean flag = false;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
count++;
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
flag = true;
}
if (!flag) {
break;
}
}
}
System.out.println("循环计算次数:" + count);
}
#输出结果(因为随机顺序的不确定性,多执行几次)
1、循环计算次数:199990000
冒泡排序耗时:1654
2、循环计算次数:19999
冒泡排序耗时:14
结论:如果生成的随机数组无序性非常大,执行的计算次数越多,最多时等于普通冒泡排序计算次数;如果是数组是有序的,则可以大大节省时间!
插入排序
插入排序中,当待排序数组是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较 N-1 次,时间复杂度为 O(N)。最坏的情况是待排序数组是逆序的,此时需要比较次数最多,最坏的情况是 O(n^2)。
public static void sort(Integer[] arr) {
int count = 0;
for (int i = 0; i < arr.length; i++) {
for (int j = i; j > 0; j--){
count++;
if (arr[j].compareTo(arr[j - 1]) < 0){
swap(arr, j, j - 1);
}else{
break;
}
}
}
System.out.println("循环计算次数:" + count);
}
#输出结果
循环计算次数:100196062
插入排序耗时:591
结论:循环计算次数相较于普通冒泡排序少了将近一半,原因就是它对于已经排好序的部分不会再次循环,而是将一个记录插入到已经排好序的有序表中,相当于变成了一个记录数增 1 的有序表
希尔排序
希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。
public static void sort(Integer[] arr) {
int count = 0;
int j;
for (int gap = arr.length / 2; gap > 0; gap = gap / 2) {
for (int i = gap; i < arr.length; i++) {
Integer tmp = arr[i];
for (j = i; j >= gap && tmp.compareTo(arr[j - gap]) < 0; j = j -gap) {
count ++;
arr[j] = arr[j - gap];
}
arr[j] = tmp;
}
}
System.out.println("循环计算次数:" + count);
}
#输出结果
循环计算次数:361088
希尔排序耗时:32
结论:比以上任何排序都快,原因就在于通过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止
|