树
节点的度:一个节点的子树数目为该节点的度,树中各节点度的最大值为树的度
树的高度:树中节点的最大层次,称为树的高度/深度
有序树:若将树中节点各子树看成是从左到右具有次序且不能交换,则该树为有序树
无序树:否则为无序树
二叉树:第i层上最多有2 IE (i-1) 个节点,深度为K的二叉树至多有2 IE (K) - 1 个节点
排序
常见算法可以分为两大类:
非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。
线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。
算法复杂度:
1、冒泡排序
思路:外层循环从1到n-1,内循环从当前外层的元素的下一个位置开始,依次和外层的元素比较,出现逆序就交换,通过与相邻元素的比较和交换来把小的数交换到最前面。
for(int i=0;i<arr.length-1;i++){
//外层循环控制排序趟数
for(int j=0;j<arr.length-1-i;j++){
//内层循环控制每一趟排序多少次
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
2、选择排序
思路:冒泡排序是通过相邻的比较和交换,每次找个最小值。选择排序是:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
private static void sort(int[] array) {
int n = array.length;
for (int i = 0; i < n-1; i++) {
int min = i;
for (int j = i+1; j < n; j++) {
if (array[j] < array[min]){
//寻找最小数
min = j; //将最小数的索引赋值
}
}
int temp = array[i];
array[i] = array[min];
array[min] = temp;
}
}
3、插入排序
思路:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。可以理解为玩扑克牌时的理牌;
private static void sort(int[] array) {
int n = array.length;
/**
*从第二位数字开始,每一个数字都试图跟它的前一个比较并交换,并重复;直到前一个数字不存在或者比它小或相等时停下来
**/
for (int i = 1; i < n; i++) {
//从第二个数开始
int key = array[i];
int j = i -1;
while (j >= 0 && array[j]>key) {
array[j + 1] = array[j]; //交换
j--; //下标向前移动
}
array[j+1] = key;
}
}
4、希尔排序
思路:希尔排序是插入排序的一种高效率的实现,也叫缩小增量排序。先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。
问题:增量的序列取法?
关于取法,没有统一标准,但最后一步必须是1;因为不同的取法涉及时间复杂度不一样,具体了解可以参考《数据结构与算法分析》;一般以length/2为算法。(再此以gap=gap*3+1为公式)
private static void sort(int[] array) {
int n = array.length;
int h = 1;
while (h<n/3) {
//动态定义间隔序列
h = 3*h +1;
}
while (h >= 1) {
for (int i = h; i < n; i++) {
for (int j = i; j >= h && (array[j] < array[j - h]); j -= h) {
int temp = array[j];
array[j] = array[j - h];
array[j-h]= temp;
}
}
h /=3;
}
}
5、归并排序
思路:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。它使用了递归分治的思想;相当于:左半边用尽,则取右半边元素;右半边用尽,则取左半边元素;右半边的当前元素小于左半边的当前元素,则取右半边元素;右半边的当前元素大于左半边的当前元素,则取左半边的元素。
自顶向下:
private static void mergeSort(int[] array) {
int[] aux = new int[array.length];
sort(array, aux, 0, array.length - 1);
}
private static void sort(int[] array, int[] aux, int lo, int hi) {
if (hi<=lo) return;
int mid = lo + (hi - lo)/2;
sort(array, aux, lo, mid);
sort(array, aux, mid + 1, hi);
merge(array, aux, lo, mid, hi);
}
private static void merge(int[] array, int[] aux, int lo, int mid, int hi) {
System.arraycopy(array,0,aux,0,array.length);
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
if (i>mid) array[k] = aux[j++];
else if (j > hi) array[k] = aux[i++];
else if (aux[j]<aux[i]) array[k] = aux[j++];
else array[k] = aux[i++];
}
}
自底向上:
public static void sort(int[] array) {
int N = a.length;
int[] aux = new int[N];
for (int n = 1; n < N; n = n+n) {
for (int i = 0; i < N-n; i += n+n) {
int lo = i;
int m = i+n-1;
int hi = Math.min(i+n+n-1, N-1);
merge(array, aux, lo, m, hi);
}
}
}
private static void merge(int[] array, int[] aux, int lo, int mid, int hi) {
for (int k = lo; k <= hi; k++) {
aux[k] = array[k];
}
// merge back to a[]
int i = lo, j = mid+1;
for (int k = lo; k <= hi; k++) {
if (i > mid) array[k] = aux[j++]; // this copying is unneccessary
else if (j > hi) array[k] = aux[i++];
else if (aux[j]<aux[i]) array[k] = aux[j++];
else array[k] = aux[i++];
}
}
缺点:因为是Out-place sort,因此相比快排,需要很多额外的空间。
为什么归并排序比快速排序慢?
答:虽然渐近复杂度一样,但是归并排序的系数比快排大。
对于归并排序有什么改进?
答:就是在数组长度为k时,用插入排序,因为插入排序适合对小数组排序。在算法导论思考题2-1中介绍了。复杂度为O(nk+nlg(n/k)) ,当k=O(lgn)时,复杂度为O(nlgn)
例子:
private static int mark = 0;
/**
* 归并排序
*/
private static int[] sort(int[] array, int low, int high) {
int mid = (low + high) / 2;
if (low < high) {
mark++;
System.out.println("正在进行第" + mark + "次分隔,得到");
System.out.println(</