数据结构基础算法整理
主要内容有:树的递归遍历、非递归借助栈遍历、非递归不借助栈遍历、层次遍历;图的深度优先遍历、广度优先遍历;八大排序算法:插入排序、希尔排序、冒泡排序、快速排序、简单选择排序、堆排序、2-路归并排序、基数排序;查找算法:二分查找
参考资料
-
树的遍历。可以分为:递归式、非递归借助栈式、非递归非借助栈式
-
先序递归遍历 。先左后右是确定的,先中后序是对根节点而言,除了先后遍历无法确定二叉树的结构,其他都可以。
public void preOrder(TreeNode root){ if(root!=null){ visit(root); preOrder(root.left); preOrder(root.right); } }
-
中序递归遍历
public void inOrder(TreeNode root){ if(root!=null){ preOrder(root.left); visit(root); preOrder(root.right); } }
-
后序递归遍历
public void postOrder(TreeNode root){ if(root!=null){ preOrder(root.left); preOrder(root.right); visit(root); } }
-
先序、中序遍历,非递归借助栈
public void preinOrder2(TreeNode root){ if(root==null) return; Stack<TreeNode> s=new Stack<>(); TreeNode p=root; while(p!=null&&!s.isEmpty()){ if(p!=null){ visit(p); //这里访问顺序是先序 s.push(p); p=p.left; }else{ p=s.pop(); visti(p); //这里访问顺序是中序 p=p.right; } } }
-
后序遍历,非递归借助栈
public void postOrder2(TreeNode root){ if(root==null) return; TreeNode cur=pre=null; Stack<TreeNode> s=new Stack<>(); s.push(root); while(!s.isEmpty()){ cur=s.peek(); //两种情况 //1.当前节点左右孩子都为空,那么可以进行访问 //2.pre不为空,且其就是自己的左孩子或有孩子 //3.第二点可能觉得有疑问:咋能说刚刚访问的是左孩子就可以访问当前节点了呢,关于这个看下面 if(cur.left==null&&cur.right==null|| pre!=null&&(cur.left==pre||cur.right==pre)){ visit(cur); pre=cur; s.pop(); }else{ //关于一个节点,若它有左右孩子,则其一定是连续入栈的,不会存在那种 //cur是当前根节点,它左孩子访问了,是pre;它右孩子还没访问的情况 if(cur.right!=null) s.push(cur.right);//这里一定要先右后左,才能保证出栈先左后右 if(cur.left!=null) s.push(cur.left); } } }
-
先序遍历,非递归非借助栈 。中序、后序的就不写了
public void preOrder3(TreeNode root){ if(root==null) return; while(root.left!=null){ visit(root); root=root.left; } visit(root); while(root!=null){ if(root.right!=null){ root=root.right; visit(root); while(root.left!=null){ root=root.left; visit(root); } }else{ TreeNode temp=null; do{ temp=root; root=root.parent; }while(root!=null&&temp==root.right) } } }
-
层次遍历,借助队列
public void levelOrder(TreeNode root){ if(root==null) return; Queue<TreeNode> q=new Queue<>(); TreeNode temp=root; q.push(temp); while(!q.isEmpty()){ temp=q.pop(); visit(temp); if(temp.left!=null) q.push(temp.left); if(temp.right!=null) q.push(temp.right); } }
-
-
图的遍历。这里只写DFS和BFS的伪代码
-
深度优先遍历
public void DFSTraverse(Graph G){ //相当于初始化一个全为false的邻接矩阵,n为节点数 boolean[] visited=new boolean[G.num*G.num]; for(int i=0;i<G.num;i++){ if(!visited[i]) DFS(G,i); } } private void DFS(Graph G, int i){ visit(i); visited[i]=true; for(w=FirstNeighbor();w>0;w=NextNeighboe()) if(!visited[w]) DFS(G,w); }
-
广度优先遍历
public void BFSTraverse(Graph G){ //相当于初始化一个全为false的邻接矩阵,n为节点数 boolean[] visited=new boolean[G.num*G.num]; for(int i=0;i<G.num;i++){ if(!visited[i]) BFS(G,i); } } private void BFS(Graph G, int i){ visit(i); visited[i]=true; q.push(i); while(!q.isEmpty()){ i=q.pop(); for(w=FirstNeighbor();w>0;w=NextNeighboe()) if(!visited[w]){ visit(w); visited[w]=true; q.push(w); } } }
-
-
八大基本排序算法
-
时空复杂度
算法 时间-最好 时间-平均 时间-最坏 空间 直接插入 n n² n² 1 希尔 / / / 1 冒泡 n n² n² 1 快排 nlogn nlogn n² logn 简单选择 n² n² n² 1 堆排序 nlogn nlogn nlogn 1 2-路归并 nlogn nlogn nlogn n 基数排序 d(n+k) d(n+k) d(n+kd) n+kd -
直接插入排序
public static void insertionSort(int[] arr){ for( int i = 1; i < arr.length; i++ ) { int temp = arr[i]; // 取出下一个元素,在已经排序的元素序列中从后向前扫描 for( int j = i; j >= 0; j-- ) { if( j > 0 && arr[j-1] > temp ) { arr[j] = arr[j-1]; } else { // 将新元素插入到该位置后 arr[j] = temp; break; } } } }
-
希尔排序
public static void shellSort(int[] arr){ int gap = arr.length / 2; for (; gap > 0; gap /= 2) { //不断缩小gap,直到1为止 for (int j = 0; (j+gap) < arr.length; j++){ //使用当前gap进行组内插入排序 for(int k = 0; (k+gap)< arr.length; k += gap){ if(arr[k] > arr[k+gap]) { int temp = arr[k+gap]; //交换操作 arr[k+gap] = arr[k]; arr[k] = temp; System.out.println(" Sorting: " + Arrays.toString(arr)); } } } } }
-
冒泡排序
public static void bubbleSort(int[] arr){ for (int i = arr.length; i > 0; i--) { //外层循环移动游标 for(int j = 0; j < i && (j+1) < i; j++){ //内层循环遍历游标及之后(或之前)的元素 if(arr[j] > arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; System.out.println("Sorting: " + Arrays.toString(arr)); } } } }
-
快速排序
public static void quickSort(int[] arr, int low, int high){ if(arr.length <= 0) return; if(low >= high) return; int left = low; int right = high; int temp = arr[left]; //挖坑1:保存基准的值 while (left < right){ //坑2:从后向前找到比基准小的元素,插入到基准位置坑1中 while(left < right && arr[right] >= temp){ right--; } arr[left] = arr[right]; //坑3:从前往后找到比基准大的元素,放到刚才挖的坑2中 while(left < right && arr[left] <= temp){ left++; } arr[right] = arr[left]; } arr[left] = temp; //基准值填补到坑3中,准备分治递归快排 System.out.println("Sorting: " + Arrays.toString(arr)); quickSort(arr, low, left-1); quickSort(arr, left+1, high); }
-
简单选择排序
public static void selectionSort(int[] arr){ for(int i = 0; i < arr.length-1; i++){ int min = i; for(int j = i+1; j < arr.length; j++){ //选出之后待排序中值最小的位置 if(arr[j] < arr[min]){ min = j; } } if(min != i){ int temp = arr[min]; //交换操作 arr[min] = arr[i]; arr[i] = temp; System.out.println("Sorting: " + Arrays.toString(arr)); } } }
-
堆排序
public static void heapSort(int[] arr){ for(int i = arr.length; i > 0; i--){ max_heapify(arr, i); int temp = arr[0]; //堆顶元素(第一个元素)与Kn交换 arr[0] = arr[i-1]; arr[i-1] = temp; } } private static void max_heapify(int[] arr, int limit){ if(arr.length <= 0 || arr.length < limit) return; int parentIdx = limit / 2; for(; parentIdx >= 0; parentIdx--){ if(parentIdx * 2 >= limit){ continue; } int left = parentIdx * 2; //左子节点位置 //右子节点位置,如果没有右节点,默认为左节点位置 int right = (left + 1) >= limit ? left : (left + 1); int maxChildId = arr[left] >= arr[right] ? left : right; if(arr[maxChildId] > arr[parentIdx]){ //交换父节点与左右子节点中的最大值 int temp = arr[parentIdx]; arr[parentIdx] = arr[maxChildId]; arr[maxChildId] = temp; } } System.out.println("Max_Heapify: " + Arrays.toString(arr)); }
-
2-路归并排序
public static int[] mergingSort(int[] arr){ if(arr.length <= 1) return arr; int num = arr.length >> 1; int[] leftArr = Arrays.copyOfRange(arr, 0, num); int[] rightArr = Arrays.copyOfRange(arr, num, arr.length); System.out.println("split two array: " + Arrays.toString(leftArr) + " And " + Arrays.toString(rightArr)); return mergeTwoArray(mergingSort(leftArr), mergingSort(rightArr)); //不断拆分为最小单元,再排序合并 } private static int[] mergeTwoArray(int[] arr1, int[] arr2){ int i = 0, j = 0, k = 0; int[] result = new int[arr1.length + arr2.length]; //申请额外的空间存储合并之后的数组 while(i < arr1.length && j < arr2.length){ //选取两个序列中的较小值放入新数组 if(arr1[i] <= arr2[j]){ result[k++] = arr1[i++]; }else{ result[k++] = arr2[j++]; } } while(i < arr1.length){ //序列1中多余的元素移入新数组 result[k++] = arr1[i++]; } while(j < arr2.length){ //序列2中多余的元素移入新数组 result[k++] = arr2[j++]; } System.out.println("Merging: " + Arrays.toString(result)); return result; }
-
基数排序
public static void radixSort(int[] arr){ if(arr.length <= 1) return; //取得数组中的最大数,并取得位数 int max = 0; for(int i = 0; i < arr.length; i++){ if(max < arr[i]){ max = arr[i]; } } int maxDigit = 1; while(max / 10 > 0){ maxDigit++; max = max / 10; } System.out.println("maxDigit: " + maxDigit); //申请一个桶空间 int[][] buckets = new int[10][arr.length]; int base = 10; //从低位到高位,对每一位遍历,将所有元素分配到桶中 for(int i = 0; i < maxDigit; i++){ int[] bktLen = new int[10]; //存储各个桶中存储元素的数量 //分配:将所有元素分配到桶中 for(int j = 0; j < arr.length; j++){ int whichBucket = (arr[j] % base) / (base / 10); buckets[whichBucket][bktLen[whichBucket]] = arr[j]; bktLen[whichBucket]++; } //收集:将不同桶里数据挨个捞出来,为下一轮高位排序做准备, //由于靠近桶底的元素排名靠前,因此从桶底先捞 int k = 0; for(int b = 0; b < buckets.length; b++){ for(int p = 0; p < bktLen[b]; p++){ arr[k++] = buckets[b][p]; } } System.out.println("Sorting: " + Arrays.toString(arr)); base *= 10; } }
-
-
查找算法。主要有二分查找、索引查找、哈希查找。这里只整理二分查找
-
二分查找
public static int commonBinarySearch(int[] arr,int key){ int low = 0; int high = arr.length - 1; int middle = 0; //定义middle if(key < arr[low] || key > arr[high] || low > high){ return -1; } while(low <= high){ middle = (low + high) / 2; if(arr[middle] > key){ //比关键字大则关键字在左区域 high = middle - 1; }else if(arr[middle] < key){ //比关键字小则关键字在右区域 low = middle + 1; }else{ return middle; } } return -1; //最后仍然没有找到,则返回-1 }
-