1. 稀疏数组和队列
1.1 稀疏数组
使用的场景
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
处理方法:
记录数组一共有几行几列,有多少个不同 的值
把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序 的规模队列
转换思路
二维->稀疏数组
遍历原始的二维数组,得到有效数据的个数sum
根据sum就可以创建稀疏数组sparseArr int[sum + 1] [3];
将二维数组的有效数据存入到稀疏数组
稀疏数组->二维数组
先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
再读取稀疏数组后几行的数据,并赋给原始的二维数组
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 public class SparseArray { public static void main (String[] args) { int chessArr1[][] = new int [11 ][11 ]; chessArr1[1 ][2 ] = 1 ; chessArr1[2 ][3 ] = 2 ; chessArr1[4 ][5 ] = 2 ; System.out.println("原始的二维数组~~" ); for (int [] row : chessArr1) { for (int data : row) { System.out.printf("%d\t" , data); } System.out.println(); } int sum = 0 ; for (int i = 0 ; i < 11 ; i++) { for (int j = 0 ; j < 11 ; j++) { if (chessArr1[i][j] != 0 ) { sum++; } } } int sparseArr[][] = new int [sum + 1 ][3 ]; sparseArr[0 ][0 ] = 11 ; sparseArr[0 ][1 ] = 11 ; sparseArr[0 ][2 ] = sum; int count = 0 ; for (int i = 0 ; i < 11 ; i++) { for (int j = 0 ; j < 11 ; j++) { if (chessArr1[i][j] != 0 ) { count++; sparseArr[count][0 ] = i; sparseArr[count][1 ] = j; sparseArr[count][2 ] = chessArr1[i][j]; } } } System.out.println(); System.out.println("稀疏数组~~~~" ); for (int i = 0 ; i < sparseArr.length; i++) { System.out.printf("%d\t%d\t%d\t\n" , sparseArr[i][0 ], sparseArr[i][1 ], sparseArr[i][2 ]); } System.out.println(); int chessArr2[][] = new int [sparseArr[0 ][0 ]][sparseArr[0 ][1 ]]; for (int i = 1 ; i < sparseArr.length; i++) { chessArr2[sparseArr[i][0 ]][sparseArr[i][1 ]] = sparseArr[i][2 ]; } System.out.println(); System.out.println("恢复后的二维数组为:" ); for (int [] row : chessArr2) { for (int data : row) { System.out.printf("%d\t" , data); } System.out.println(); } } }
1.2 队列
队列概念
队列是一个有序列表 ,可以用数组 或是链表 来实现。
遵循先入先出 的原则。即:先存入队列的数据,要先取出。后存入的要后取 出
示意图:(使用数组模拟队列示意图)
数组模拟队列思路
队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图, 其中 maxSize 是该队列的最大容量。
因为队列的输出、输入是分别从前后端来处理,因此需要两个变量 front 及 rear 分别记录队列前后端的下标,front 会随着数据输出而改变,而 rear 则是随着数据输入而改变
当我们将数据存入队列时称为”addQueue”,addQueue 的处理需要有两个步骤:
将尾指针往后移:rear+1 , 当 front == rear (空) (这里的front是有数据的前一个位置)
若尾指针 rear 小于队列的最大下标 maxSize-1,则将数据存入 rear 所指的数组元素中,否则无法存入数据。rear == maxSize - 1(队列满)
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 class ArrayQueue { private int maxSize; private int front; private int rear; private int [] arr; public ArrayQueue (int arrMaxSize) { maxSize = arrMaxSize; arr = new int [maxSize]; front = -1 ; rear = -1 ; } public boolean isFull () { return rear == maxSize - 1 ; } public boolean isEmpty () { return rear == front; } public void addQueue (int n) { if (isFull()) { System.out.println("队列已满~" ); return ; } rear++; arr[rear] = n; } public int getQueue () { if (isEmpty()) { throw new RuntimeException("队列为空,没有数据" ); } front++; return arr[front]; } public void showQueue () { if (isEmpty()) { System.out.println("队列为空没有数据~~" ); return ; } for (int i = 0 ; i < arr.length; i++) { System.out.printf("arr[%d]=%d\n" , i, arr[i]); } } public int headQueue () { if (isEmpty()) { throw new RuntimeException("队列为空没有数据~~" ); } return arr[front + 1 ]; } }
数组模拟环形队列
front变量指向队首元素 ,初值为0
rear变量指向队尾元素的下一个元素 ,初值为0。规定空出一个位置
队列为空的判定条件:front == rear
队列为满的判定条件:(rear + 1) % maxSize == front
队列中有效元素的个数:(rear - front + maxSize) % maxSize
入队和出队时,都需要让标记对maxSize取模
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 class CircleArray { private int maxSize; private int front; private int rear; private int [] arr; public CircleArray (int arrMaxSize) { maxSize = arrMaxSize; arr = new int [maxSize]; } public boolean isFull () { return (rear + 1 ) % maxSize == front; } public boolean isEmpty () { return rear == front; } public void addQueue (int n) { if (isFull()) { System.out.println("队列已满,没有数据~" ); return ; } arr[rear] = n; rear = (rear + 1 ) % maxSize; } public int getQueue () { if (isEmpty()) { throw new RuntimeException("队列为空,没有数据" ); } int value = arr[front]; front = (front + 1 ) % maxSize; return value; } public void showQueue () { if (isEmpty()) { System.out.println("队列为空~~" ); return ; } for (int i = front; i < front + size() ; i++) { System.out.printf("arr[%d]=%d\n" , i % maxSize, arr[i % maxSize]); } } public int size () { return (rear + maxSize - front) % maxSize; } public int headQueue () { if (isEmpty()) { throw new RuntimeException("队列为空~~" ); } return arr[front]; } }
2. 链表
2.1 单链表
简单的介绍
链表的逻辑结构示意图如下:
实现思路
增加
直接添加到尾部,把上一个节点的指针指向新的节点
根据排名添加节点(插入)
先遍历到需要插入夫人地方,将新的节点先连接到下一个节点上,然后再把上一个节点指向这个节点,
heroNode.next = temp.next; //先连接
temp.next = heroNode; //再断开
修改节点
遍历找到需要修改的节点,修改信息即可
删除节点
找到需要删除的节点的上一个节点,把当前节点(要删除节点的上一个节点)指向需要删除节点的一个节点的上一个节点
temp.next = temp.next.next;
逆序打印链表
使用栈的先进后出的特性来实现
查找链表的倒数第几个节点
倒数第几个节点就是顺数第(size-index)个节点,先遍历一遍求得长度,然后求第(size-index)个节点就行
翻转链表
感觉和前面的插入没有什么不同,就是需要一个next 来暂存下一个节点,防止断开后找不到下一个节点的位置
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 package com.Mercury.linkedlist;import java.util.Stack;public class SingleLinkedListDemo { public static void main (String[] args) { HeroNode hero1 = new HeroNode(1 , "宋江" , "及时雨" ); HeroNode hero2 = new HeroNode(2 , "卢俊义" , "玉麒麟" ); HeroNode hero3 = new HeroNode(3 , "吴用" , "智多星" ); HeroNode hero4 = new HeroNode(4 , "林冲" , "豹子头" ); SingleLinkedList singleLinkedList = new SingleLinkedList(); singleLinkedList.add(hero1); singleLinkedList.add(hero4); singleLinkedList.add(hero2); singleLinkedList.add(hero3); System.out.println("链表数据如下~~" ); singleLinkedList.list(); System.out.println("逆序打印链表, 没有打印链表的结构~~" ); reversePrint(singleLinkedList.getHead()); System.out.println("翻转后的链表为" ); reversetList(singleLinkedList.getHead()); singleLinkedList.list(); SingleLinkedList singleLinkedList2 = new SingleLinkedList(); singleLinkedList2.addByOrder(hero1); singleLinkedList2.addByOrder(hero4); singleLinkedList2.addByOrder(hero2); singleLinkedList2.addByOrder(hero3); HeroNode newHeroNode = new HeroNode(2 , "小卢" , "玉麒麟~~" ); singleLinkedList2.update(newHeroNode); System.out.println("修改后的~~" ); singleLinkedList2.list(); singleLinkedList2.del(1 ); singleLinkedList2.del(4 ); System.out.println("删除后的节点为~~" ); singleLinkedList2.list(); System.out.println("有效节点数=" + getLength(singleLinkedList2.getHead())); HeroNode res = findLastIndexNode(singleLinkedList2.getHead(), 2 ); System.out.println("res=" + res); } public static void reversePrint (HeroNode head) { if (head.next == null ) { return ; } Stack<HeroNode> stack = new Stack<HeroNode>(); HeroNode cur = head.next; while (cur != null ) { stack.push(cur); cur = cur.next; } while (stack.size() > 0 ) { System.out.println(stack.pop()); } } public static void reversetList (HeroNode head) { if (head.next == null || head.next.next == null ) { return ; } HeroNode cur = head.next; HeroNode next = null ; HeroNode reverseHead = new HeroNode(0 , "" , "" ); while (cur != null ) { next = cur.next; cur.next = reverseHead.next; reverseHead.next = cur; cur = next; } head.next = reverseHead.next; } public static HeroNode findLastIndexNode (HeroNode head, int index) { if (head.next == null ) { return null ; } int size = getLength(head); if (index <=0 || index > size) { return null ; } HeroNode cur = head.next; for (int i =0 ; i< size - index; i++) { cur = cur.next; } return cur; } public static int getLength (HeroNode head) { if (head.next == null ) { return 0 ; } int length = 0 ; HeroNode cur = head.next; while (cur != null ) { length++; cur = cur.next; } return length; } }class SingleLinkedList { private HeroNode head = new HeroNode(0 , "" , "" ); public HeroNode getHead () { return head; } public void add (HeroNode heroNode) { HeroNode temp = head; while (true ) { if (temp.next == null ) { break ; } temp = temp.next; } temp.next = heroNode; } public void addByOrder (HeroNode heroNode) { HeroNode temp = head; boolean flag = false ; while (true ) { if (temp.next == null ) { break ; } if (temp.next.no > heroNode.no) { break ; } else if (temp.next.no == heroNode.no) { flag = true ; break ; } temp = temp.next; } if (flag) { System.out.printf("准备插入英雄编号 %d 已经存在, 不能再加入\n" , heroNode.no); } else { heroNode.next = temp.next; temp.next = heroNode; } } public void update (HeroNode newHeroNode) { if (head.next == null ) { System.out.println("链表为空~" ); return ; } HeroNode temp = head.next; boolean flag = false ; while (true ) { if (temp == null ) { break ; } if (temp.no == newHeroNode.no) { flag = true ; break ; } temp = temp.next; } if (flag) { temp.name = newHeroNode.name; temp.nickname = newHeroNode.nickname; } else { System.out.printf("没有找到编号为 %d 的节点\n" , newHeroNode.no); } } public void del (int no) { HeroNode temp = head; boolean flag = false ; while (true ) { if (temp.next == null ) { break ; } if (temp.next.no == no) { flag = true ; break ; } temp = temp.next; } if (flag) { temp.next = temp.next.next; }else { System.out.printf("没有找到编号为 %d 的节点\n" , no); } } public void list () { if (head.next == null ) { System.out.println("链表为空" ); return ; } HeroNode temp = head.next; while (true ) { if (temp == null ) { break ; } System.out.println(temp); temp = temp.next; } } }class HeroNode { public int no; public String name; public String nickname; public HeroNode next; public HeroNode (int no, String name, String nickname) { this .no = no; this .name = name; this .nickname = nickname; } @Override public String toString () { return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]" ; } }
输出的数据如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 链表数据如下~~ HeroNode [no=1 , name=宋江, nickname=及时雨] HeroNode [no=4 , name=林冲, nickname=豹子头] HeroNode [no=2 , name=卢俊义, nickname=玉麒麟] HeroNode [no=3 , name=吴用, nickname=智多星] 逆序打印链表, 没有打印链表的结构~~ HeroNode [no=3 , name=吴用, nickname=智多星] HeroNode [no=2 , name=卢俊义, nickname=玉麒麟] HeroNode [no=4 , name=林冲, nickname=豹子头] HeroNode [no=1 , name=宋江, nickname=及时雨] 翻转后的链表为 HeroNode [no=3 , name=吴用, nickname=智多星] HeroNode [no=2 , name=卢俊义, nickname=玉麒麟] HeroNode [no=4 , name=林冲, nickname=豹子头] HeroNode [no=1 , name=宋江, nickname=及时雨] 修改后的~~ HeroNode [no=1 , name=宋江, nickname=及时雨] HeroNode [no=2 , name=小卢, nickname=玉麒麟~~] HeroNode [no=3 , name=吴用, nickname=智多星] HeroNode [no=4 , name=林冲, nickname=豹子头] 删除后的节点为~~ HeroNode [no=2 , name=小卢, nickname=玉麒麟~~] HeroNode [no=3 , name=吴用, nickname=智多星] 有效节点数=2 res=HeroNode [no=2 , name=小卢, nickname=玉麒麟~~]
2.2 双向链表
简单的介绍
管理单向链表的缺点分析:
单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
单向链表不能自我删除,需要靠辅助节点 ,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到 temp,temp 是待删除节点的前一个节点(认真体会).
思路实现
遍历 方和 单链表一样,只是可以向前,也可以向后查找
添加 (默认添加到双向链表的最后)
先找到双向链表的最后这个节点
temp.next = newHeroNode
newHeroNode.pre = temp;
修改 思路和 原来的单向链表一样.
删除
因为是双向链表,因此,我们可以实现自我删除某个节点直接找到要删除的这个节点,比如 temp
temp.pre.next = temp.next
temp.next.pre = temp.pre;
如果这里是最后一个节点的话需要额外判断一下,否则会出现空指针异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 package com.Mercury.linkedlist;public class DoubleLinkedListDemo { public static void main (String[] args) { HeroNode2 hero1 = new HeroNode2(1 , "宋江" , "及时雨" ); HeroNode2 hero2 = new HeroNode2(2 , "卢俊义" , "玉麒麟" ); HeroNode2 hero3 = new HeroNode2(3 , "吴用" , "智多星" ); HeroNode2 hero4 = new HeroNode2(4 , "林冲" , "豹子头" ); DoubleLinkedList doubleLinkedList = new DoubleLinkedList(); doubleLinkedList.add(hero1); doubleLinkedList.add(hero2); doubleLinkedList.add(hero3); doubleLinkedList.add(hero4); doubleLinkedList.list(); HeroNode2 newHeroNode = new HeroNode2(4 , "公孙胜" , "入云龙" ); doubleLinkedList.update(newHeroNode); System.out.println("修改后的链表情况" ); doubleLinkedList.list(); doubleLinkedList.del(3 ); System.out.println("删除后的链表情况~~" ); doubleLinkedList.list(); } }class DoubleLinkedList { private HeroNode2 head = new HeroNode2(0 , "" , "" ); public HeroNode2 getHead () { return head; } public void list () { if (head.next == null ) { System.out.println("双向链表为空" ); return ; } HeroNode2 temp = head.next; while (true ) { if (temp == null ) { break ; } System.out.println(temp); temp = temp.next; } } public void add (HeroNode2 heroNode) { HeroNode2 temp = head; while (true ) { if (temp.next == null ) { break ; } temp = temp.next; } temp.next = heroNode; heroNode.pre = temp; } public void update (HeroNode2 newHeroNode) { if (head.next == null ) { System.out.println("链表为空~" ); return ; } HeroNode2 temp = head.next; boolean flag = false ; while (true ) { if (temp == null ) { break ; } if (temp.no == newHeroNode.no) { flag = true ; break ; } temp = temp.next; } if (flag) { temp.name = newHeroNode.name; temp.nickname = newHeroNode.nickname; } else { System.out.printf("没有找到编号为 %d 的节点\n" , newHeroNode.no); } } public void del (int no) { if (head.next == null ) { System.out.println("双向链表为空" ); return ; } HeroNode2 temp = head.next; boolean flag = false ; while (true ) { if (temp == null ) { break ; } if (temp.no == no) { flag = true ; break ; } temp = temp.next; } if (flag) { temp.pre.next = temp.next; if (temp.next != null ) { temp.next.pre = temp.pre; } } else { System.out.printf("没有找到编号为 %d 的节点\n" , no); } } }class HeroNode2 { public int no; public String name; public String nickname; public HeroNode2 next; public HeroNode2 pre; public HeroNode2 (int no, String name, String nickname) { this .no = no; this .name = name; this .nickname = nickname; } @Override public String toString () { return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]" ; } }
2.3 约瑟夫环
问题介绍
Josephu 问题为:设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(1<=k<=n)的人从 1 开始报数,数到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
思路实现
使用单向环形链表实现,每报到一个数的时候就让这个节点打印出来,然后删除节点,直到只有最后一个节点
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 package com.Mercury.linkedlist;public class Josepfu { public static void main (String[] args) { CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList(); circleSingleLinkedList.addBoy(5 ); circleSingleLinkedList.countBoy(1 , 2 , 5 ); } }class CircleSingleLinkedList { private Boy first = null ; public void addBoy (int nums) { if (nums < 1 ) { System.out.println("nums 输入有误,请重新输入" ); return ; } Boy curBoy = null ; for (int i = 1 ; i <= nums; i++) { Boy boy = new Boy(i); if (i == 1 ) { first = boy; first.setNext(first); curBoy = first; } else { curBoy.setNext(boy); boy.setNext(first); curBoy = boy; } } } public void showBoy () { if (first == null ) { System.out.println("当前链表为空~~" ); return ; } Boy curBoy = first; while (true ) { System.out.printf("当前小孩的编号为 %d \n" , curBoy.getNo()); if (curBoy.getNext() == first) { break ; } curBoy = curBoy.getNext(); } } public void countBoy (int startNo, int countNum, int nums) { if (first == null || startNo < 1 || startNo > nums) { System.out.println("链表为空或输入的编号有误" ); return ; } Boy helper = first; while (true ) { if (helper.getNext() == first) { break ; } helper = helper.getNext(); } for (int j = 0 ; j < startNo - 1 ; j++) { first = first.getNext(); helper = helper.getNext(); } while (true ) { if (helper == first) { break ; } for (int j = 0 ; j < countNum - 1 ; j++) { first = first.getNext(); helper = helper.getNext(); } System.out.printf("出环的孩子编号为 %d \n" , first.getNo()); first = first.getNext(); helper.setNext(first); } System.out.printf("最后出环的孩子编号为 %d \n" , first.getNo()); } }class Boy { private int no; private Boy next; public Boy (int no) { this .no = no; } public int getNo () { return no; } public void setNo (int no) { this .no = no; } public Boy getNext () { return next; } public void setNext (Boy next) { this .next = next; } }
3. 栈
3.1 初识栈
什么是栈
栈是一种先进后出的有序列表,限制删除和增加的操作只能在同一端的特殊线性表,变化的一端就是栈顶,另一端被我们称为栈底。
栈的应用场景
子程序的调用
处理递归调用
表达式的求值
二叉树的遍历
图的深度优先搜索
实现思路
使用数组来模拟栈
栈顶定义为top,初始值设为-1
入栈 top++ ; stack[top] = data;
出栈 int value = stack[top]; top–;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 class ArrayStack { private int maxSize; private int [] stack; private int top = -1 ; public ArrayStack (int maxSize) { this .maxSize = maxSize; stack = new int [this .maxSize]; } public boolean isFull () { return top == maxSize - 1 ; } public boolean isEmpty () { return top == -1 ; } public void push (int value) { if (isFull()) { System.out.println("栈已经满了" ); return ; } top++; stack[top] = value; } public int pop () { if (isEmpty()){ throw new RuntimeException("栈已经空了" ); } int value = stack[top]; top--; return value; } public void list () { if (isEmpty()){ throw new RuntimeException("栈为空" ); } for (int i = top; i >= 0 ; i--) { System.out.printf("stack[%d]=%d\n" , i, stack[i]); } } }public class ArrayStackDemo { public static void main (String[] args) { ArrayStack arrayStack = new ArrayStack(4 ); arrayStack.push(10 ); arrayStack.push(9 ); arrayStack.push(4 ); System.out.println(arrayStack.pop()); System.out.println(arrayStack.pop()); System.out.println(arrayStack.pop()); } }
3.2 表达式求值
表达式求值是栈的经典的应用之一,简单的来说,就是把一长串的数学表达式,通过程序计算出来,实现一个计算机的功能
实现的思路
建立两个栈来分别存储数字和运算符号,并使用一个索引遍历表达式
遇到数字直接就进入数栈
遇到符号
如果符号栈是空的,直接入符号栈
如果不是空的,当前的符号的优先级小于或等于栈顶的元素,符号栈弹出一个符号,数字栈弹出两个数字计算,运算结果进入数字栈,然后当前的符号入符号栈
如果不是空的,当前的符号的优先级大于栈顶的元素,当前的元素直接入符号栈
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 package com.Mercury.stack;public class Calculator { public static void main (String[] args) { String expression = "7*2*2-5+1-5+3-4" ; ArrayStack2 numStack = new ArrayStack2(10 ); ArrayStack2 operStack = new ArrayStack2(10 ); int index = 0 ; int num1 = 0 ; int num2 = 0 ; int oper = 0 ; int res = 0 ; char ch = ' ' ; String keepNum = "" ; while (true ) { ch = expression.substring(index, index+1 ).charAt(0 ); if (operStack.isOper(ch)) { if (!operStack.isEmpty()) { if (operStack.priority(ch) <= operStack.priority(operStack.peek())) { num1 = numStack.pop(); num2 = numStack.pop(); oper = operStack.pop(); res = numStack.cal(num1, num2, oper); numStack.push(res); operStack.push(ch); } else { operStack.push(ch); } }else { operStack.push(ch); } } else { keepNum += ch; if (index == expression.length() - 1 ) { numStack.push(Integer.parseInt(keepNum)); }else { if (operStack.isOper(expression.substring(index+1 ,index+2 ).charAt(0 ))) { numStack.push(Integer.parseInt(keepNum)); keepNum = "" ; } } } index++; if (index >= expression.length()) { break ; } } while (true ) { if (operStack.isEmpty()) { break ; } num1 = numStack.pop(); num2 = numStack.pop(); oper = operStack.pop(); res = numStack.cal(num1, num2, oper); numStack.push(res); } int res2 = numStack.pop(); System.out.printf(" %s = %d" , expression, res2); } }class ArrayStack2 { private int maxSize; private int [] stack; private int top = -1 ; public ArrayStack2 (int maxSize) { this .maxSize = maxSize; stack = new int [this .maxSize]; } public int peek () { return stack[top]; } public boolean isFull () { return top == maxSize - 1 ; } public boolean isEmpty () { return top == -1 ; } public void push (int value) { if (isFull()) { System.out.println("栈是满的" ); return ; } top++; stack[top] = value; } public int pop () { if (isEmpty()) { throw new RuntimeException("栈是空的~" ); } int value = stack[top]; top--; return value; } public void list () { if (isEmpty()) { System.out.println("栈是空的~~" ); return ; } for (int i = top; i >= 0 ; i--) { System.out.printf("stack[%d]=%d\n" , i, stack[i]); } } public int priority (int oper) { if (oper == '*' || oper == '/' ){ return 1 ; } else if (oper == '+' || oper == '-' ) { return 0 ; } else { return -1 ; } } public boolean isOper (char val) { return val == '+' || val == '-' || val == '*' || val == '/' ; } public int cal (int num1, int num2, int oper) { int res = 0 ; switch (oper) { case '+' : res = num1 + num2; break ; case '-' : res = num2 - num1; break ; case '*' : res = num1 * num2; break ; case '/' : res = num2 / num1; break ; default : break ; } return res; } }
3.3 中缀转后缀
后缀表达式(逆波兰表达式)的计算
从左到右扫描后缀表达式,遇到数字入栈,遇到符号,弹出两个数字计算,将结果入栈,最后得出最后结果,
实现思路
初始化一个符号栈s1,一个存储中间结果的栈s2,从左到右扫描表达式
遇到数字,直接压入s2
遇到运算符
s1为空,s1栈顶为左括号’ ( ',当前符号比栈顶运算符优先级高,直接入栈s1
否则(s1栈顶的元素优先级小于或等于当前的元素的优先级)将s1栈顶的运算符弹出并压入s2中,再次与s1中的栈顶元素比较
遇到括号
遇到左括号’(’ 直接压入s1
遇到右括号 ‘)’ 依次弹出s1栈顶的运算符并压入s2,直到遇到左括号为止,然后将这一对括号丢弃
将s1中剩余的运算符依次弹出并压入s2
依次弹出s2中的元素并输出,结果的逆序就是结果
1+( ( 2 + 3 ) * 4 ) - 5 的转换过程
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 public class PolandNotation { public static void main (String[] args) { String expression = "1+((2+3)*4)-5" ; List<String> infixExpressionList = toInfixExpressionList(expression); System.out.println("中缀表达式 List=" + infixExpressionList); List<String> suffixExpreesionList = parseSuffixExpreesionList(infixExpressionList); System.out.println("后缀表达式 List" + suffixExpreesionList); System.out.printf("expression=%d" , calculate(suffixExpreesionList)); } public static List<String> toInfixExpressionList (String s) { List<String> ls = new ArrayList<String>(); int i = 0 ; String str; char c; do { if ((c=s.charAt(i)) < 48 || (c=s.charAt(i)) > 57 ) { ls.add("" + c); i++; } else { str = "" ; while (i < s.length() && (c=s.charAt(i)) >= 48 && (c=s.charAt(i)) <= 57 ) { str += c; i++; } ls.add(str); } }while (i < s.length()); return ls; } public static List<String> parseSuffixExpreesionList (List<String> ls) { Stack<String> s1 = new Stack<String>(); List<String> s2 = new ArrayList<String>(); for (String item: ls) { if (item.matches("\\d+" )) { s2.add(item); } else if (item.equals("(" )) { s1.push(item); } else if (item.equals(")" )) { while (!s1.peek().equals("(" )) { s2.add(s1.pop()); } s1.pop(); } else { while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item) ) { s2.add(s1.pop()); } s1.push(item); } } while (s1.size() != 0 ) { s2.add(s1.pop()); } return s2; } public static List<String> getListString (String suffixExpression) { String[] split = suffixExpression.split(" " ); List<String> list = new ArrayList<String>(); for (String ele: split) { list.add(ele); } return list; } public static int calculate (List<String> ls) { Stack<String> stack = new Stack<String>(); for (String item : ls) { if (item.matches("\\d+" )) { stack.push(item); } else { int num2 = Integer.parseInt(stack.pop()); int num1 = Integer.parseInt(stack.pop()); int res = 0 ; if (item.equals("+" )) { res = num1 + num2; } else if (item.equals("-" )) { res = num1 - num2; } else if (item.equals("*" )) { res = num1 * num2; } else if (item.equals("/" )) { res = num1 / num2; } else { throw new RuntimeException("符号非法" ); } stack.push("" + res); } } return Integer.parseInt(stack.pop()); } }class Operation { private static int ADD = 1 ; private static int SUB = 1 ; private static int MUL = 2 ; private static int DIV = 2 ; public static int getValue (String operation) { int result = 0 ; switch (operation) { case "+" : result = ADD; break ; case "-" : result = SUB; break ; case "*" : result = MUL; break ; case "/" : result = DIV; break ; default : break ; } return result; } }
1 2 3 中缀表达式 List=[1 , +, (, (, 2 , +, 3 , ), *, 4 , ), -, 5 ] 后缀表达式 List[1 , 2 , 3 , +, 4 , *, +, 5 , -] expression=16
4. 递归
4.1 初识递归
递归的简单介绍
简单的来说就是自己调用自己,每次调用的时候都传入不同的变量,递归有助于解决,复杂的问题,虽然他自己空间复杂度太高有时会导致算法的效率不高
递归需要遵守的规则
执行一个方法的时候,就创建一个新的受保护的独立空间(栈空间)
方法的局部变量是独立的
如果方法中使用的是引用类型的变量(比如数组),就会共享该类型的的数据
递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError
当一个方法执行完毕,或者遇到return的时候,就会返回,遵守谁调用就返回给谁的原则
4.2 迷宫问题
从迷宫的左上角走到右下角,问你路径(这里还不是最短路径)
实现思路
使用1标记墙,0代表没有走过的路,2代表走过可行的路,3代表走不通的路
使用递归依次遍历下,右,上,左四个方向,如果走不通则标记当前为3,返回上一级,重新走
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class MiGong { public static void main (String[] args) { int [][] map = new int [8 ][7 ]; for (int i = 0 ;i<7 ;i++){ map[0 ][i] = 1 ; map[7 ][i] = 1 ; } for (int i = 0 ;i<8 ;i++){ map[i][0 ] = 1 ; map[i][6 ] = 1 ; } map[3 ][1 ] = 1 ; map[3 ][2 ] = 1 ; setway(map,1 ,1 ); for (int i = 0 ; i < 8 ; i++) { for (int j = 0 ; j < 7 ; j++) { System.out.print(map[i][j] + " " ); } System.out.println(); } } public static boolean setway (int map[][], int i, int j) { if (map[6 ][5 ] == 2 ){ return true ; } if (map[i][j] == 0 ){ map[i][j] = 2 ; if (setway(map,i+1 ,j)){ return true ; }else if (setway(map,i,j+1 )){ return true ; }else if (setway(map,i-1 ,j)){ return true ; }else if (setway(map,i,j-1 )){ return true ; }else { map[i][j] = 3 ; return false ; } }else { return false ; } } }
1 2 3 4 5 6 7 8 1 1 1 1 1 1 1 1 2 0 0 0 0 1 1 2 2 2 0 0 1 1 1 1 2 0 0 1 1 0 0 2 0 0 1 1 0 0 2 0 0 1 1 0 0 2 2 2 1 1 1 1 1 1 1 1
4.3 八皇后问题
问题描述
这个问题是一个经典的问题,回溯算法的经典案例,即在一个8*8的棋盘上面,摆放8个皇后棋子,这8个棋子不能在同一行,不能再同一列,也不能在同一条斜线上,问一共有几种摆放的方法
实现思路
第一个棋子先放在第一行第一列
第二个棋子放在第二行第一列,判断是不是可行,不可行就放在第二行第二列,继续判断
继续上述步骤,直到摆放完8个棋子,得到一个可行的结果,如果都不可行,就返回上一个步骤,放在下一个位置
一直回溯,直到找到全部的解
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class Queue8 { int max = 8 ; int [] array = new int [max]; static int count = 0 ; public static void main (String[] args) { Queue8 queue = new Queue8(); queue.cheak(0 ); System.out.println("总数为 :" +count); } public void cheak (int x) { if (x == 8 ){ print(array); count++; return ; } for (int i = 0 ; i < max; i++) { array[x] = i; if (judge(x)){ cheak(x+1 ); } } } public boolean judge (int n) { for (int i = 0 ; i < n; i++) { if (array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i]) ) { return false ; } } return true ; } public void print (int [] array) { for (int i = 0 ; i < 8 ; i++) { System.out.print(" " + array[i]); } System.out.println(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 0 4 7 5 2 6 1 3 0 5 7 2 6 3 1 4 0 6 3 5 7 1 4 2 0 6 4 7 1 3 5 2 1 3 5 7 2 0 6 4 1 4 6 0 2 7 5 3 1 4 6 3 0 7 5 2 1 5 0 6 3 7 2 4 1 5 7 2 0 3 6 4 1 6 2 5 7 4 0 3 1 6 4 7 0 3 5 2 1 7 5 0 2 4 6 3 2 0 6 4 7 1 3 5 2 4 1 7 0 6 3 5 2 4 1 7 5 3 6 0 2 4 6 0 3 1 7 5 2 4 7 3 0 6 1 5 2 5 1 4 7 0 6 3 2 5 1 6 0 3 7 4 2 5 1 6 4 0 7 3 2 5 3 0 7 4 6 1 2 5 3 1 7 4 6 0 2 5 7 0 3 6 4 1 2 5 7 0 4 6 1 3 2 5 7 1 3 0 6 4 2 6 1 7 4 0 3 5 2 6 1 7 5 3 0 4 2 7 3 6 0 5 1 4 3 0 4 7 1 6 2 5 3 0 4 7 5 2 6 1 3 1 4 7 5 0 2 6 3 1 6 2 5 7 0 4 3 1 6 2 5 7 4 0 3 1 6 4 0 7 5 2 3 1 7 4 6 0 2 5 3 1 7 5 0 2 4 6 3 5 0 4 1 7 2 6 3 5 7 1 6 0 2 4 3 5 7 2 0 6 4 1 3 6 0 7 4 1 5 2 3 6 2 7 1 4 0 5 3 6 4 1 5 0 2 7 3 6 4 2 0 5 7 1 3 7 0 2 5 1 6 4 3 7 0 4 6 1 5 2 3 7 4 2 0 6 1 5 4 0 3 5 7 1 6 2 4 0 7 3 1 6 2 5 4 0 7 5 2 6 1 3 4 1 3 5 7 2 0 6 4 1 3 6 2 7 5 0 4 1 5 0 6 3 7 2 4 1 7 0 3 6 2 5 4 2 0 5 7 1 3 6 4 2 0 6 1 7 5 3 4 2 7 3 6 0 5 1 4 6 0 2 7 5 3 1 4 6 0 3 1 7 5 2 4 6 1 3 7 0 2 5 4 6 1 5 2 0 3 7 4 6 1 5 2 0 7 3 4 6 3 0 2 7 5 1 4 7 3 0 2 5 1 6 4 7 3 0 6 1 5 2 5 0 4 1 7 2 6 3 5 1 6 0 2 4 7 3 5 1 6 0 3 7 4 2 5 2 0 6 4 7 1 3 5 2 0 7 3 1 6 4 5 2 0 7 4 1 3 6 5 2 4 6 0 3 1 7 5 2 4 7 0 3 1 6 5 2 6 1 3 7 0 4 5 2 6 1 7 4 0 3 5 2 6 3 0 7 1 4 5 3 0 4 7 1 6 2 5 3 1 7 4 6 0 2 5 3 6 0 2 4 1 7 5 3 6 0 7 1 4 2 5 7 1 3 0 6 4 2 6 0 2 7 5 3 1 4 6 1 3 0 7 4 2 5 6 1 5 2 0 3 7 4 6 2 0 5 7 4 1 3 6 2 7 1 4 0 5 3 6 3 1 4 7 0 2 5 6 3 1 7 5 0 2 4 6 4 2 0 5 7 1 3 7 1 3 0 6 4 2 5 7 1 4 2 0 6 3 5 7 2 0 5 1 4 6 3 7 3 0 2 5 1 6 4 总数为 :92
5. 排序算法
5.1 算法的时间复杂度
我们一般使用O()来表示算法的时间复杂度
推导时间复杂度
用1取代所有的加法常数
只保留最高阶项
去掉最高阶项的常数(非常数的时候)
常数阶
比如一般的分支结构都是O(1)
1 2 int sum = 0 ; System.out.println(sum);
线性阶
循环结构就是O(n)
1 2 3 for (int i = 0 ;i<n;i++){ System.out.println(i); }
对数阶
下面的这写代码,每次循环count就变为两倍,则运行的次数就是2 x = n 2^x=n 2 x = n ,所以x = l o g 2 n x=log_2 n x = l o g 2 n ,时间复杂度就是O(log n)
1 2 3 4 int count = 1 ;while (count<n){ count = count*2 ; }
平方阶
简单来说就是双重循环就是平方阶了,当然还是得按照推导方法来推导
常见的时间度
O(1) < O(logn)< O(n)< O(nlogn)< O(n 2 n^2 n 2 )< O(n 3 n^3 n 3 )< O(2 n 2^n 2 n )< O(n!)< O(n n n^n n n )
算法的空间复杂度
衡量算法性能的另一个指示标准,算法的空间复杂度的计算公式记作S(n) = O(f(n)) ; n为问题的规模,f(n)为语句关于n所占存储空间的函数
各个算法的动画演示
由于这个博客系统的原因,动图显示不出来,因此在这里贴一个动图连接数据结构与算法系列–十大排序(附动态图解) - 知乎 (zhihu.com)
5.2 冒泡排序
冒泡简介
冒泡排序应该说是程序员的入门排序算法了,他的基本的思想就是,从数组的较小的元素开始,依次比较相邻的两个元素的值,如果是逆序,则把两个值交换,
因为需要比较很多次,因此效率很低。
优化
如果一趟比较下来没有进行过交换,就说明序列有序,因此可以通过设置一个flag来判断,用来减少不必要的交换
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class BubbleSort { public static void main (String[] args) { int arr[] = {3 ,9 ,-1 ,10 ,20 }; bubblesort(arr); System.out.println(Arrays.toString(arr)); int [] testArr = new int [80000 ]; for (int i =0 ; i < 80000 ;i++) { testArr[i] = (int )(Math.random() * 8000000 ); } long startTime=System.currentTimeMillis(); bubblesort(testArr); long endTime=System.currentTimeMillis(); System.out.println("程序运行时间: " +(endTime-startTime)+"ms" ); } public static void bubblesort (int [] arr) { int temp = 0 ; boolean flag = false ; for (int i = 0 ; i < arr.length-1 ; i++) { for (int j = i+1 ; j < arr.length; j++) { if (arr[i]<arr[j]){ flag = true ; temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } if (!flag){ break ; }else { flag = false ; } } } }
1 2 [20 , 10 , 9 , 3 , -1 ] 八万个随机数据程序运行时间: 7760ms
5.3 选择排序
选择排序简介
选择排序也属于内部排序法,按指定的规则选出一个元素,再依次按规定交换位置,达到排序的目的,他的基本的思想就是从要开始的排序的下标开始,把当前的数据看作是最小的数据,然后遍历后面的数据,并且不断刷新最小值,遍历完成后,与刚刚位置上面的值相比,如果没有变,就不做处理,否则交换。第二个位置则不断刷新后面未排序的里面的最小值,重复上述操作,直到排序完成。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class SelectSort { public static void main (String[] args) { int [] arr = {101 , 34 , 119 , 1 , -1 , 90 , 123 }; selectsort(arr); System.out.println(Arrays.toString(arr)); int [] testArr = new int [80000 ]; for (int i =0 ; i < 80000 ;i++) { testArr[i] = (int )(Math.random() * 8000000 ); } long startTime=System.currentTimeMillis(); selectsort(testArr); long endTime=System.currentTimeMillis(); System.out.println("程序运行时间: " +(endTime-startTime)+"ms" ); } public static void selectsort (int arr[]) { int min; int minIndex; for (int i = 0 ; i < arr.length-1 ; i++) { min = arr[i]; minIndex = i; for (int j = i+1 ; j < arr.length; j++) { if (min > arr[j]){ min = arr[j]; minIndex = j; } } if (min != arr[i]){ arr[minIndex] = arr[i]; arr[i] = min; } } } }
1 2 [-1 , 1 , 34 , 90 , 101 , 119 , 123 ] 八万个随机数据程序运行时间: 1999ms
5.4 插入排序
插入排序简介
插入排序也属于内部排序,基本思想就是把已经排好的数据看作一组,把未排序的数据一个一个在里面找到属于自己的合适位置,并在这个过程中,把已经排好顺序的数据后移,直到那个数据找到合适的位置插入进去。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class InsertSort { public static void main (String[] args) { int [] arr = {101 , 34 , 119 , 1 , -1 , 89 }; insertsort(arr); System.out.println(Arrays.toString(arr)); int [] testArr = new int [80000 ]; for (int i = 0 ; i < 80000 ; i++) { testArr[i] = (int ) (Math.random() * 8000000 ); } long startTime=System.currentTimeMillis(); insertsort(testArr); long endTime=System.currentTimeMillis(); System.out.println("程序运行时间: " +(endTime-startTime)+"ms" ); } public static void insertsort (int [] arr) { int insertVal; int insertIndex; for (int i = 1 ; i < arr.length; i++) { insertVal = arr[i]; insertIndex = i - 1 ; while (insertIndex >= 0 && insertVal < arr[insertIndex]) { arr[insertIndex + 1 ] = arr[insertIndex]; insertIndex--; } if (insertIndex + 1 != i) { arr[insertIndex + 1 ] = insertVal; } } } }
1 2 [-1 , 1 , 34 , 89 , 101 , 119 ] 八万个随机数据程序运行时间: 437ms
5.5 希尔排序
希尔排序简介
很明显,希尔排序是由希尔这个人提出来的,他是简单插入排序的经过改进后的一个更高效的版本,也被称为缩小增量排序 。他的基本思想就是,把数组数据按照下标的一些分组,不断进行插入排序,分组逐渐减少,当减少到1的时候,算法便终止。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public class ShellSort { public static void main (String[] args) { int [] arr = {8 , 9 , 1 , 7 , 2 , 3 , 5 , 4 , 6 , 0 }; shellsortSwap(arr); System.out.println(Arrays.toString(arr)); int [] arr2 = {8 , 9 , 1 , 7 , 2 , 3 , 5 , 4 , 6 , 0 }; shellsortInsert(arr2); System.out.println(Arrays.toString(arr2)); int [] testArr = new int [80000 ]; for (int i = 0 ; i < 80000 ; i++) { testArr[i] = (int ) (Math.random() * 8000000 ); } long startTime=System.currentTimeMillis(); shellsortInsert(testArr); long endTime=System.currentTimeMillis(); System.out.println("程序运行时间: " +(endTime-startTime)+"ms" ); } public static void shellsortSwap (int [] arr) { int temp; for (int gap = arr.length / 2 ; gap > 0 ; gap /= 2 ) { for (int i = 0 ; i < arr.length; i++) { for (int j = i - gap; j >= 0 ; j -= gap) { if (arr[j] > arr[j + gap]) { temp = arr[j + gap]; arr[j + gap] = arr[j]; arr[j] = temp; } } } } } public static void shellsortInsert (int [] arr) { for (int gap = arr.length / 2 ; gap > 0 ; gap /= 2 ) { for (int i = gap; i < arr.length; i++) { int insertVal = arr[i]; int insertIndex = i; if (arr[insertIndex] < arr[insertIndex - gap]) { while (insertIndex - gap >= 0 && insertVal < arr[insertIndex - gap]) { arr[insertIndex] = arr[insertIndex - gap]; insertIndex -= gap; } } arr[insertIndex] = insertVal; } } }
1 2 3 [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] 程序运行时间: 17ms
5.6 快速排序
快速排序简介
快速排序是对冒泡排序的一种改进,基本思想就是把将要排序的数据分为两组,其中一部分的数据比另外的一部分都要小,然后把这两组数据分别再次进行相同的操作,直到排序完成,整个过程通过递归实现。快排是一种不稳定排序
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public class QuickSort { public static void main (String[] args) { int [] arr = {-9 , 78 , 0 , 23 , -567 , 70 , -1 , 900 , 4561 }; quicksort(arr, 0 , arr.length - 1 ); System.out.println(Arrays.toString(arr)); int [] testArr = new int [80000 ]; for (int i = 0 ; i < 80000 ; i++) { testArr[i] = (int ) (Math.random() * 8000000 ); } long startTime = System.currentTimeMillis(); quicksort(testArr, 0 , testArr.length - 1 ); long endTime = System.currentTimeMillis(); System.out.println("程序运行时间: " + (endTime - startTime) + "ms" ); } public static void quicksort (int [] arr, int low, int high) { int pivot; if (low < high) { pivot = Partition(arr, low, high); quicksort(arr, low, pivot - 1 ); quicksort(arr, pivot + 1 , high); } } public static int Partition (int [] arr, int low, int high) { int pivotkey; pivotkey = arr[low]; while (low < high) { while (low < high && arr[high] >= pivotkey) { high--; } Swap(arr, low, high); while (low < high && arr[low] <= pivotkey) { low++; } Swap(arr, low, high); } return low; } public static void Swap (int [] arr, int a, int b) { int temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; } }
1 2 [-567 , -9 , -1 , 0 , 23 , 70 , 78 , 900 , 4561 ] 程序运行时间: 11ms
5.7 归并排序
归并排序简介
这是一种利用归并思想实现的排序算法,该算法采用经典的分治思想,(分)先把数据分成很小的部分,(治)然后分别排序,即分而治之。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 public class MergetSort { public static void main (String[] args) { int arr[] = { 8 , 4 , 5 , 7 , 1 , 3 , 6 , 2 }; int temp[] = new int [arr.length]; mergeSort(arr, 0 , arr.length - 1 ,temp); System.out.println(Arrays.toString(arr)); int [] testArr = new int [80000 ]; for (int i = 0 ; i < 80000 ; i++) { testArr[i] = (int ) (Math.random() * 8000000 ); } int testTemp[] = new int [testArr.length]; long startTime = System.currentTimeMillis(); mergeSort(testArr, 0 , testArr.length - 1 ,testTemp); long endTime = System.currentTimeMillis(); System.out.println("程序运行时间: " + (endTime - startTime) + "ms" ); } public static void mergeSort (int [] arr, int left, int right, int [] temp) { if (left < right) { int mid = (left + right) / 2 ; mergeSort(arr, left, mid, temp); mergeSort(arr, mid + 1 , right, temp); merge(arr, left, mid, right, temp); } } public static void merge (int [] arr, int left, int mid, int right, int [] temp) { int i = left; int j = mid + 1 ; int t = 0 ; while (i <= mid && j <= right) { if (arr[i] <= arr[j]) { temp[t] = arr[i]; t += 1 ; i += 1 ; } else { temp[t] = arr[j]; t += 1 ; j += 1 ; } } while ( i <= mid) { temp[t] = arr[i]; t += 1 ; i += 1 ; } while ( j <= right) { temp[t] = arr[j]; t += 1 ; j += 1 ; } t = 0 ; int tempLeft = left; while (tempLeft <= right) { arr[tempLeft] = temp[t]; t += 1 ; tempLeft += 1 ; } } }
1 2 [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ] 程序运行时间: 12ms
5.8 基数排序
基数排序简介
基数排序是稳定的排序算法,是桶排序的拓展。基本的思想就是把每个数字切割出来,然后将他们按照个位大小放入桶中,排完之后,取出放入数组,然后比较十位大小,直到排序完。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public class RadixSort { public static void main (String[] args) { int arr[] = { 53 , 3 , 542 , 748 , 14 , 214 }; radixSort(arr); System.out.println(Arrays.toString(arr)); int [] testArr = new int [80000 ]; for (int i = 0 ; i < 80000 ; i++) { testArr[i] = (int ) (Math.random() * 8000000 ); } int testTemp[] = new int [testArr.length]; long startTime = System.currentTimeMillis(); radixSort(testArr); long endTime = System.currentTimeMillis(); System.out.println("程序运行时间: " + (endTime - startTime) + "ms" ); } public static void radixSort (int [] arr) { int max = arr[0 ]; for (int i = 1 ; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } int maxLength = (max + "" ).length(); int [][] bucket = new int [10 ][arr.length]; int [] bucketElementCounts = new int [10 ]; for (int i = 0 , n = 1 ; i < maxLength; i++, n *= 10 ) { for (int j = 0 ; j < arr.length; j++) { int digitOfElement = arr[j] / n % 10 ; bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j]; bucketElementCounts[digitOfElement]++; } int index = 0 ; for (int k = 0 ; k < bucketElementCounts.length; k++) { if (bucketElementCounts[k] != 0 ) { for (int l = 0 ; l < bucketElementCounts[k]; l++) { arr[index++] = bucket[k][l]; } } bucketElementCounts[k] = 0 ; } } } }
1 2 [3 , 14 , 53 , 214 , 542 , 748 ] 程序运行时间: 16ms
5.9 排序算法总结
相关属术语解释:
稳定:如果 a 原本在 b 前面,而 a == b ,排序之后 a 仍然在 b 的前面;
不稳定:如果 a 原本在 b 的前面,而 a == b ,排序之后 a 可能会出现在 b 的后面;
内排序:所有排序操作都在内存中完成;
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度:一个算法执行所耗费的时间。
空间复杂度:运行完一个程序所需内存的大小。
n :数据规模
k :“桶”的个数
In - place : 不占用额外内存
Out - place :占用额外内存
6. 查找
6.1 二分查找
实现思路
首先通过左右两个索引确定数组的下标,即mid
$ mid = low + F(k - 1) -1 $
让target与中间值对比,如果target大于该值,则继续往前二分查找,否则,就往后继续二分查找
如果中间值==target说明目标找到,结束递归,或者左边的索引已经大于了右边的索引,说明没有找到,结束递归
特别注意:二分查找的前提是这一系列的数据都已经排序好,后续的几个查找,都需要排序好
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class BinarySearch { public static void main (String[] args) { int arr[] = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 }; int resIndex = binarySearch(arr, 0 , arr.length - 1 , 17 ); System.out.println("resIndex=" + resIndex); int arr2[] = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 20 , 20 , 20 }; List<Integer> resIndexList = binarySearch2(arr2, 0 , arr.length - 1 , 20 ); System.out.println("resIndex=" + resIndexList); } public static int binarySearch (int [] arr, int low, int high, int target) { if (low > high || target > arr[arr.length - 1 ] || target < arr[0 ]) { return -1 ; } int mid = (low + high) / 2 ; if (arr[mid] > target) { return binarySearch(arr, low, mid - 1 , target); } else if (arr[mid] < target) { return binarySearch(arr, mid + 1 , high, target); } else { return mid; } } public static List<Integer> binarySearch2 (int [] arr, int low, int high, int target) { if (low > high || target > arr[arr.length - 1 ] || target < arr[0 ]) { return new ArrayList<Integer>(); } int mid = (low + high) / 2 ; if (arr[mid] > target) { return binarySearch2(arr, low, mid - 1 , target); } else if (arr[mid] < target) { return binarySearch2(arr, mid + 1 , high, target); } else { ArrayList<Integer> resIndexlist = new ArrayList<>(); int temp = mid - 1 ; while (true ) { if (temp < 0 || arr[temp] != target) { break ; } resIndexlist.add(temp); temp -= 1 ; } resIndexlist.add(mid); temp = mid +1 ; while (true ) { if (temp >= arr.length || arr[temp] != target) { break ; } resIndexlist.add(temp); temp += 1 ; } return resIndexlist; } } }
1 2 resIndex=16 resIndex=[17 , 18 , 19 ]
6.2 插值查找
实现思路
简单的来说,就是把上述二分查找的mid索引,换成了一个新的公式算出来的新索引,其他的都一样
$ mid = low + \frac{target - arr[low]}{arr[high] - arr[low]} * (high - low) $
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class InsertValueSearch { public static void main (String[] args) { int arr[] = {1 , 8 , 10 , 89 , 1000 , 1234 }; int resIndex = insertvalueSearch(arr,0 , arr.length-1 ,1234 ); System.out.println(resIndex); } public static int insertvalueSearch (int [] arr, int low, int high, int target) { if (target < arr[0 ] || target > arr[arr.length - 1 ] || low > high) { return -1 ; } int mid = low + (high - low) * (target - arr[low]) / (arr[high] - arr[low]); if (target > arr[mid]) { return insertvalueSearch(arr, mid + 1 , high, target); } else if (target < arr[mid]) { return insertvalueSearch(arr, low, mid - 1 , target); } else { return mid; } } }
6.3 斐波那契查找
实现思路
斐波那契查找与前面两种查找类似,仅仅只是改变了中间索引mid的位置,这个索引现在位于黄金分割点附近,即
$ mid = low + F(k - 1) -1 $
感觉花里胡哨,并没有提高多少查找效率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public class FibonacciSearch { static int massize = 20 ; public static void main (String[] args) { int [] arr = {1 , 8 , 10 , 89 , 1000 , 1234 }; int resIndex = fibonaccisearch(arr, 1000 ); System.out.println(resIndex); } public static int [] creatfibnacci() { int [] f = new int [massize]; f[0 ] = 0 ; f[1 ] = 1 ; for (int i = 2 ; i < massize; i++) { f[i] = f[i - 1 ] + f[i - 2 ]; } return f; } public static int fibonaccisearch (int [] arr, int target) { int f[] = creatfibnacci(); int low = 0 ; int high = arr.length - 1 ; int k = 0 ; int mid = 0 ; while (high > f[k] - 1 ) { k++; } int [] temp = Arrays.copyOf(arr, f[k]); for (int i = high + 1 ; i < f[k]; i++) { temp[i] = arr[high]; } while (low <= high) { mid = low + f[k - 1 ] - 1 ; if (temp[mid] > target) { high = mid - 1 ; k -= 1 ; } else if (temp[mid] < target) { low = mid + 1 ; k -= 2 ; } else { if (mid < high) { return mid; } else { return high; } } } return -1 ; } }
7. 哈希表
7.1 哈希表的简单介绍
散列表,也叫哈希表,是根据关键码值二进行访问的数据结构,也就是说,它通过关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 public class HashTabDemo { public static void main (String[] args) { HashTab hashTab = new HashTab(7 ); String key = "" ; Scanner scanner = new Scanner(System.in); while (true ) { System.out.println("add: 添加数据" ); System.out.println("list: 展示" ); System.out.println("find: 查找" ); System.out.println("exit: 退出" ); key = scanner.next(); switch (key) { case "add" : System.out.println("输入id" ); int id = scanner.nextInt(); System.out.println("输入姓名" ); String name = scanner.next(); Emp emp = new Emp(id, name); hashTab.add(emp); break ; case "list" : hashTab.list(); break ; case "find" : System.out.println("输入查找id" ); id = scanner.nextInt(); hashTab.findEmpById(id); break ; case "exit" : scanner.close(); System.exit(0 ); default : break ; } } } }class HashTab { private EmpLinkedList[] empLinkedListArray; private int size; public HashTab (int size) { this .size = size; empLinkedListArray = new EmpLinkedList[size]; for (int i = 0 ; i < size; i++) { empLinkedListArray[i] = new EmpLinkedList(); } } public void add (Emp emp) { int empLinkedListNO = hashFun(emp.id); empLinkedListArray[empLinkedListNO].add(emp); } public void list () { for (int i = 0 ; i < size; i++) { empLinkedListArray[i].list(i); } } public void findEmpById (int id) { int empLinkedListNO = hashFun(id); Emp emp = empLinkedListArray[empLinkedListNO].findEmpById(id); if (emp != null ) { System.out.printf("在第%d条链表找到id = %d\n" , (empLinkedListNO + 1 ), id); }else { System.out.println("没有找到该雇员" ); } } public int hashFun (int id) { return id % size; } }class Emp { public int id; public String name; public Emp next; public Emp (int id, String name) { super (); this .id = id; this .name = name; } }class EmpLinkedList { private Emp head; public void add (Emp emp) { if (head == null ) { head = emp; return ; } Emp curEmp = head; while (true ) { if (curEmp.next == null ) { break ; } curEmp = curEmp.next; } curEmp.next = emp; } public void list (int no) { if (head == null ) { System.out.println("第" +(no+1 )+"链表为空" ); return ; } System.out.print("第" +(no+1 )+"条链表 信息为" ); Emp curEmp = head; while (true ) { System.out.printf(" => id=%d name=%s\t" , curEmp.id, curEmp.name); if (curEmp.next == null ) { break ; } curEmp = curEmp.next; } System.out.println(); } public Emp findEmpById (int id) { if (head == null ) { System.out.println("空表" ); return null ; } Emp curEmp = head; while (true ) { if (curEmp.id == id) { break ; } if (curEmp.next == null ) { curEmp = null ; break ; } curEmp = curEmp.next; } return curEmp; } }
8. 树
8.1 树的基本介绍
树的基本介绍,在另一篇博客中有详细的介绍,这里不在详述
8.2 二叉树的前中后序遍历
简单的来说,就是根节点的输出的顺序不同导致的,比如前序遍历就是 根-左-右 ,中序遍历就是 左-右-根 ,后序遍历就是 左-右-根 ,在代码的实现过程中,主要体现在写递归的时候,输出函数的位置不同
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 class BinaryTree { private HeroNode root; public void setRoot (HeroNode root) { this .root = root; } public void preOrder () { if (this .root != null ) { this .root.preOrder(); }else { System.out.println("该树为空" ); } } public void infixOrder () { if (this .root != null ) { this .root.infixOrder(); }else { System.out.println("该树为空" ); } } public void postOrder () { if (this .root != null ) { this .root.postOrder(); }else { System.out.println("该树为空" ); } } }class HeroNode { private int no; private String name; private HeroNode left; private HeroNode right; public HeroNode (int no, String name) { this .no = no; this .name = name; } public int getNo () { return no; } public void setNo (int no) { this .no = no; } public String getName () { return name; } public void setName (String name) { this .name = name; } public HeroNode getLeft () { return left; } public void setLeft (HeroNode left) { this .left = left; } public HeroNode getRight () { return right; } public void setRight (HeroNode right) { this .right = right; } @Override public String toString () { return "HeroNode [no=" + no + ", name=" + name + "]" ; } public void preOrder () { System.out.println(this ); if (this .left != null ) { this .left.preOrder(); } if (this .right != null ) { this .right.preOrder(); } } public void infixOrder () { if (this .left != null ) { this .left.infixOrder(); } System.out.println(this ); if (this .right != null ) { this .right.infixOrder(); } } public void postOrder () { if (this .left != null ) { this .left.postOrder(); } if (this .right != null ) { this .right.postOrder(); } System.out.println(this ); }
8.3 二叉树的查找指定的节点
实现思路
我自我感觉就是和前面的相比,就是把前面前中后序遍历中的输出变成了寻找到相应节点的递归出口,其他并么有多大的变化
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public HeroNode preOrderSearch (int no) { if (this .no == no) { return this ; } HeroNode resNode = null ; if (this .left != null ) { resNode = this .left.preOrderSearch(no); } if (resNode != null ) { return resNode; } if (this .right != null ) { resNode = this .right.preOrderSearch(no); } return resNode; } public HeroNode infixOrderSearch (int no) { HeroNode resNode = null ; if (this .left != null ) { resNode = this .left.infixOrderSearch(no); } if (resNode != null ) { return resNode; } if (this .no == no) { return this ; } if (this .right != null ) { resNode = this .right.infixOrderSearch(no); } return resNode; } public HeroNode postOrderSearch (int no) { HeroNode resNode = null ; if (this .left != null ) { resNode = this .left.postOrderSearch(no); } if (resNode != null ) { return resNode; } if (this .right != null ) { resNode = this .right.postOrderSearch(no); } if (resNode != null ) { return resNode; } if (this .no == no) { return this ; } return resNode; }
8.4 二叉树删除相应的节点
实现思路
如果是空树root,如果只有一个root节点,则等价于将二叉树置空
因为是单项的树,所以删除的时候需要寻找待删除节点的上一个节点
在每个节点里写好删除的方法,如果下一个节点不为空并且数字符合,那么就置空,然后返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class binaryTree 中:public void delNode (int no ) { if (root != null ) { if (root.getNo() == no) { root = null ; } else { root.delNode(no); } }else { System.out.println("空树,不能删除" ); } }class node 中:public void delNode (int no ) { if (this .left != null && this .left.no == no) { this .left = null ; return ; } if (this .right != null && this .right.no == no) { this .right = null ; return ; } if (this .left != null ) { this .left.delNode(no); } if (this .right != null ) { this .right.delNode(no); } }
8.5 堆排序
堆排序实际上也是一种选择排序,它也是不稳定排序。堆是具有以下性质的完全二叉树,每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆,每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆。
实现思路
将待排序序列构造成一个大顶堆,此时整个序列的最大值就是堆顶的根节点
将堆顶的元素与末尾元素进行交换,此时末尾就为最大值
将除去最后一个已排序的节点的其他元素继续构造为一个大顶堆,再与堆的最后一个元素交换
持续上述操作,直至成为有序序列
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class HeapSort { public static void main (String[] args) { int arr[] = {3 ,9 ,-1 ,10 ,20 }; heapSort(arr); System.out.println(Arrays.toString(arr)); int [] testArr = new int [80000 ]; for (int i =0 ; i < 80000 ;i++) { testArr[i] = (int )(Math.random() * 8000000 ); } long startTime=System.currentTimeMillis(); heapSort(testArr); long endTime=System.currentTimeMillis(); System.out.println("八万个随机数据程序运行时间: " +(endTime-startTime)+"ms" ); } public static void heapSort (int arr[]) { int temp = 0 ; for (int i = arr.length / 2 -1 ; i >=0 ; i--) { adjustHeap(arr, i, arr.length); } for (int j = arr.length-1 ;j >0 ; j--) { temp = arr[j]; arr[j] = arr[0 ]; arr[0 ] = temp; adjustHeap(arr, 0 , j); } } public static void adjustHeap (int arr[], int i, int lenght) { int temp = arr[i]; for (int k = i * 2 + 1 ; k < lenght; k = k * 2 + 1 ) { if (k+1 < lenght && arr[k] < arr[k+1 ]) { k++; } if (arr[k] > temp) { arr[i] = arr[k]; i = k; } else { break ; } } arr[i] = temp; } }
8.6 赫夫曼树
基本介绍
给定n个权值作为n个叶子节点,构造一棵二叉树,若该树的带权路径长度达到最小 ,称这样的树为最优二叉树,也称赫夫曼树
实现思路
将每个数据看作是一棵最简单的二叉树,不断取出根节点权值最小的两颗二叉树,组成一棵新的二叉树,该颗二叉树根节点的权值是前面两颗二叉树根节点权值的和,然后再将这颗新的二叉树放回去,再次排序,不断选出最小的两棵二叉树组合。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 public class HuffmanTree { public static void main (String[] args) { int arr[] = { 13 , 7 , 8 , 3 , 29 , 6 , 1 }; Node root = createHuffmanTree(arr); preOrder(root); } public static void preOrder (Node root) { if (root != null ) { root.preOrder(); }else { System.out.println("kono空树da~~" ); } } public static Node createHuffmanTree (int [] arr) { List<Node> nodes = new ArrayList<Node>(); for (int value : arr) { nodes.add(new Node(value)); } while (nodes.size() > 1 ) { Collections.sort(nodes); System.out.println("nodes =" + nodes); Node leftNode = nodes.get(0 ); Node rightNode = nodes.get(1 ); Node parent = new Node(leftNode.value + rightNode.value); parent.left = leftNode; parent.right = rightNode; nodes.remove(leftNode); nodes.remove(rightNode); nodes.add(parent); } return nodes.get(0 ); } }class Node implements Comparable <Node > { int value; char c; Node left; Node right; public void preOrder () { System.out.println(this ); if (this .left != null ) { this .left.preOrder(); } if (this .right != null ) { this .right.preOrder(); } } public Node (int value) { this .value = value; } @Override public String toString () { return "Node [value=" + value + "]" ; } @Override public int compareTo (Node o) { return this .value - o.value; } }
8.7 二叉排序树
二叉排序树介绍
二叉排序数(BST : Binary Sort Tree) 对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值要大
二叉排序树的实现思路
创建
在创建的时候不断比较,比节点小的就不断去左边,直到找到合适的位置,右边同理
删除
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 public class BinarySortTreeDemo { public static void main (String[] args) { int [] arr = {7 , 3 , 10 , 12 , 5 , 1 , 9 , 2 }; BinarySortTree binarySortTree = new BinarySortTree(); for (int i = 0 ; i< arr.length; i++) { binarySortTree.add(new Node(arr[i])); } System.out.println("中序遍历~" ); binarySortTree.infixOrder(); binarySortTree.delNode(12 ); binarySortTree.delNode(5 ); binarySortTree.delNode(10 ); binarySortTree.delNode(2 ); binarySortTree.delNode(3 ); binarySortTree.delNode(9 ); binarySortTree.delNode(1 ); binarySortTree.delNode(7 ); System.out.println("root=" + binarySortTree.getRoot()); System.out.println("中序遍历" ); binarySortTree.infixOrder(); } }class BinarySortTree { private Node root; public Node getRoot () { return root; } public Node search (int value) { if (root == null ) { return null ; } else { return root.search(value); } } public Node searchParent (int value) { if (root == null ) { return null ; } else { return root.searchParent(value); } } public int delRightTreeMin (Node node) { Node target = node; while (target.left != null ) { target = target.left; } delNode(target.value); return target.value; } public void delNode (int value) { if (root == null ) { return ; }else { Node targetNode = search(value); if (targetNode == null ) { return ; } if (root.left == null && root.right == null ) { root = null ; return ; } Node parent = searchParent(value); if (targetNode.left == null && targetNode.right == null ) { if (parent.left != null && parent.left.value == value) { parent.left = null ; } else if (parent.right != null && parent.right.value == value) { parent.right = null ; } } else if (targetNode.left != null && targetNode.right != null ) { int minVal = delRightTreeMin(targetNode.right); targetNode.value = minVal; } else { if (targetNode.left != null ) { if (parent != null ) { if (parent.left.value == value) { parent.left = targetNode.left; } else { parent.right = targetNode.left; } } else { root = targetNode.left; } } else { if (parent != null ) { if (parent.left.value == value) { parent.left = targetNode.right; } else { parent.right = targetNode.right; } } else { root = targetNode.right; } } } } } public void add (Node node) { if (root == null ) { root = node; } else { root.add(node); } } public void infixOrder () { if (root != null ) { root.infixOrder(); } else { System.out.println("kono空树da~" ); } } }class Node { int value; Node left; Node right; public Node (int value) { this .value = value; } public Node search (int value) { if (value == this .value) { return this ; } else if (value < this .value) { if (this .left == null ) { return null ; } return this .left.search(value); } else { if (this .right == null ) { return null ; } return this .right.search(value); } } public Node searchParent (int value) { if ((this .left != null && this .left.value == value) || (this .right != null && this .right.value == value)) { return this ; } else { if (value < this .value && this .left != null ) { return this .left.searchParent(value); } else if (value >= this .value && this .right != null ) { return this .right.searchParent(value); } else { return null ; } } } @Override public String toString () { return "Node [value=" + value + "]" ; } public void add (Node node) { if (node == null ) { return ; } if (node.value < this .value) { if (this .left == null ) { this .left = node; } else { this .left.add(node); } } else { if (this .right == null ) { this .right = node; } else { this .right.add(node); } } } public void infixOrder () { if (this .left != null ) { this .left.infixOrder(); } System.out.println(this ); if (this .right != null ) { this .right.infixOrder(); } } }
8.8 平衡二叉树
简单的介绍
平衡二叉树(AVL树)也叫二叉搜索树,可以保证查询的效率高,平衡二叉树具有以下特点:他是一棵空树或者他的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是平衡二叉树,平衡二叉树的实现方法有 红黑树,AVL,替罪羊树,Treap,伸展树等。
实现思路
平衡二叉树可以通过上面的二叉排序树旋转而来,通常有左旋转,右旋转,双旋转。
左旋转
当则右子树的高度大于左子树的高度的时候,我们就需要左旋转来换个根节点,将右子树的高度压下去。
右旋转
当左子树的高度大于右子树的高度的时候,这个时候就需要进行右旋转,用来把左子树的高度压下去
双旋转
有时候,你进行上述两个操作之后,发现仍然没有达到平衡二叉树的要求,这个时候就需要双旋转的出场了,出现这个问题就是因为它的左子树的右子树的高度大于了它左子树左子树的高度,或者相反,都会导致这种情况的发生。解决的思路就是先对当前这个节点进行左旋转(或者右旋转),再对当前节点进行右旋转(或者相反)
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 public class AVLTreeDemo { public static void main (String[] args) { int [] arr = {10 , 11 , 7 , 6 , 8 , 9 }; AVLTree avlTree = new AVLTree(); for (int i = 0 ; i < arr.length; i++) { avlTree.add(new Node(arr[i])); } System.out.println("中序遍历" ); avlTree.infixOrder(); System.out.println("在平衡处理后:" ); System.out.println("树的高度=" + avlTree.getRoot().height()); System.out.println("左子树的高度=" + avlTree.getRoot().leftHeight()); System.out.println("右子树的高度=" + avlTree.getRoot().rightHeight()); System.out.println("当前的根节点=" + avlTree.getRoot()); } }class AVLTree { private Node root; public Node getRoot () { return root; } public Node search (int value) { if (root == null ) { return null ; } else { return root.search(value); } } public Node searchParent (int value) { if (root == null ) { return null ; } else { return root.searchParent(value); } } public int delRightTreeMin (Node node) { Node target = node; while (target.left != null ) { target = target.left; } delNode(target.value); return target.value; } public void delNode (int value) { if (root == null ) { return ; } else { Node targetNode = search(value); if (targetNode == null ) { return ; } if (root.left == null && root.right == null ) { root = null ; return ; } Node parent = searchParent(value); if (targetNode.left == null && targetNode.right == null ) { if (parent.left != null && parent.left.value == value) { parent.left = null ; } else if (parent.right != null && parent.right.value == value) { parent.right = null ; } } else if (targetNode.left != null && targetNode.right != null ) { int minVal = delRightTreeMin(targetNode.right); targetNode.value = minVal; } else { if (targetNode.left != null ) { if (parent != null ) { if (parent.left.value == value) { parent.left = targetNode.left; } else { parent.right = targetNode.left; } } else { root = targetNode.left; } } else { if (parent != null ) { if (parent.left.value == value) { parent.left = targetNode.right; } else { parent.right = targetNode.right; } } else { root = targetNode.right; } } } } } public void add (Node node) { if (root == null ) { root = node; } else { root.add(node); } } public void infixOrder () { if (root != null ) { root.infixOrder(); } else { System.out.println("这怕是个空树" ); } } }class Node { int value; Node left; Node right; public Node (int value) { this .value = value; } public int leftHeight () { if (left == null ) { return 0 ; } return left.height(); } public int rightHeight () { if (right == null ) { return 0 ; } return right.height(); } public int height () { return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1 ; } private void leftRotate () { Node newNode = new Node(value); newNode.left = left; newNode.right = right.left; value = right.value; right = right.right; left = newNode; } private void rightRotate () { Node newNode = new Node(value); newNode.right = right; newNode.left = left.right; value = left.value; left = left.left; right = newNode; } public Node search (int value) { if (value == this .value) { return this ; } else if (value < this .value) { if (this .left == null ) { return null ; } return this .left.search(value); } else { if (this .right == null ) { return null ; } return this .right.search(value); } } public Node searchParent (int value) { if ((this .left != null && this .left.value == value) || (this .right != null && this .right.value == value)) { return this ; } else { if (value < this .value && this .left != null ) { return this .left.searchParent(value); } else if (value >= this .value && this .right != null ) { return this .right.searchParent(value); } else { return null ; } } } @Override public String toString () { return "Node [value=" + value + "]" ; } public void add (Node node) { if (node == null ) { return ; } if (node.value < this .value) { if (this .left == null ) { this .left = node; } else { this .left.add(node); } } else { if (this .right == null ) { this .right = node; } else { this .right.add(node); } } if (rightHeight() - leftHeight() > 1 ) { if (right != null && right.leftHeight() > right.rightHeight()) { right.rightRotate(); leftRotate(); } else { leftRotate(); } return ; } if (leftHeight() - rightHeight() > 1 ) { if (left != null && left.rightHeight() > left.leftHeight()) { left.leftRotate(); rightRotate(); } else { rightRotate(); } } } public void infixOrder () { if (this .left != null ) { this .left.infixOrder(); } System.out.println(this ); if (this .right != null ) { this .right.infixOrder(); } } }
8.9 多路查找树
B树
B树通过重新组织节点,降低了树的高度,文件系统及数据库系统设计者利用了磁盘预读原理,将一个节点的大小设为等于一个页(页得大小通常为4k)这样每个节点只需要一次I/O就可以完全载入,将树得度M设置1024,在600亿个元素最多只需要4次I/O操作就可以读取到想要得元素,B树(B+)广泛应用于文件存储系统以及数据库系统中,B树得阶就是节点最多子节点个数
B+树
B+树是B树得变种,也是一种多路查找树。B+树得搜索与b树基本相同,区别是B+树只有达到叶子节点才命中,所有关键字都出现在叶子节点得链表中(也叫做稠密索引),且链表中的关键字恰好是有序的,非叶子节点相当于是叶子节点的索引(稀疏索引),叶子节点相当于是存储数据的数据层,更适合文件索引系统
B *树
B *树是B+树的变体,在B+树的非根和非叶子节点再增加指向兄弟的指针,B *树定义了非叶子节点关键字至少为(2/3) * M,即块的最低使用率为2/3,而B+树的块的最低使用率为1/2
9. 图
简单的介绍
图是一种数据结构,其中节点可以具有零个或多个相邻的元素。两个节点之间的连接称为边
图的其他术语不再详细介绍,在这里,我们采用邻接矩阵的方式来表示图,其中1表示连接,0表示没有连接
图的深度优先遍历(DFS)
主要是可以递归(栈)实现,从初识节点初识访问节点开始,接着访问它的第一个邻接节点,接着再以这个被访问的节点为初始节点,接着访问下一个邻接节点,如果这个邻接节点不存在(不相连),就退回上一个节点,访问和它相连的第二个邻接节点
图的广度优先遍历(BFS)
主要通过队列实现,类似于一个分层搜索的过程,将第一个节点(root)入队,然后将和它相连所有节点入队,一个接着一个访问,并且把所有未访问的并且和该节点相连的节点都入队,进行访问,直到访问完毕。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 public class Graph { private ArrayList<String> vertexList; private int [][] edges; private int numOfEdges; private boolean [] isVisited; public static void main (String[] args) { int n = 8 ; String Vertexs[] = {"1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" }; Graph graph = new Graph(n); for (String vertex: Vertexs) { graph.insertVertex(vertex); } graph.insertEdge(0 , 1 , 1 ); graph.insertEdge(0 , 2 , 1 ); graph.insertEdge(1 , 3 , 1 ); graph.insertEdge(1 , 4 , 1 ); graph.insertEdge(3 , 7 , 1 ); graph.insertEdge(4 , 7 , 1 ); graph.insertEdge(2 , 5 , 1 ); graph.insertEdge(2 , 6 , 1 ); graph.insertEdge(5 , 6 , 1 ); graph.showGraph(); System.out.println("dfs" ); graph.dfs(); System.out.println(); System.out.println("bfs" ); graph.bfs(); } public Graph (int n) { edges = new int [n][n]; vertexList = new ArrayList<String>(n); numOfEdges = 0 ; } public int getFirstNeighbor (int index) { for (int j = 0 ; j < vertexList.size(); j++) { if (edges[index][j] > 0 ) { return j; } } return -1 ; } public int getNextNeighbor (int v1, int v2) { for (int j = v2 + 1 ; j < vertexList.size(); j++) { if (edges[v1][j] > 0 ) { return j; } } return -1 ; } private void dfs (boolean [] isVisited, int i) { System.out.print(getValueByIndex(i) + "->" ); isVisited[i] = true ; int w = getFirstNeighbor(i); while (w != -1 ) { if (!isVisited[w]) { dfs(isVisited, w); } w = getNextNeighbor(i, w); } } public void dfs () { isVisited = new boolean [vertexList.size()]; for (int i = 0 ; i < getNumOfVertex(); i++) { if (!isVisited[i]) { dfs(isVisited, i); } } } private void bfs (boolean [] isVisited, int i) { int u ; int w ; LinkedList queue = new LinkedList(); System.out.print(getValueByIndex(i) + "=>" ); isVisited[i] = true ; queue.addLast(i); while ( !queue.isEmpty()) { u = (Integer)queue.removeFirst(); w = getFirstNeighbor(u); while (w != -1 ) { if (!isVisited[w]) { System.out.print(getValueByIndex(w) + "=>" ); isVisited[w] = true ; queue.addLast(w); } w = getNextNeighbor(u, w); } } } public void bfs () { isVisited = new boolean [vertexList.size()]; for (int i = 0 ; i < getNumOfVertex(); i++) { if (!isVisited[i]) { bfs(isVisited, i); } } } public int getNumOfVertex () { return vertexList.size(); } public void showGraph () { for (int [] link : edges) { System.err.println(Arrays.toString(link)); } } public int getNumOfEdges () { return numOfEdges; } public String getValueByIndex (int i) { return vertexList.get(i); } public int getWeight (int v1, int v2) { return edges[v1][v2]; } public void insertVertex (String vertex) { vertexList.add(vertex); } public void insertEdge (int v1, int v2, int weight) { edges[v1][v2] = weight; edges[v2][v1] = weight; numOfEdges++; } }
10. 常用(大概吧)算法
这里写的算法以及示例都是相对比较浅的,就看一遍的话,是不可能掌握的,要想真正的掌握,成竹在胸,必须要多刷题!!!
二分查找非递归
相比较之前的递归,这里采用的是使用循环实现二分查找,原理都是一样的,关键是mid值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static int binarySearch (int [] arr, int target) { int left = 0 ; int right = arr.length - 1 ; while (left <= right) { int mid = (left + right) / 2 ; if (arr[mid] == target) { return mid; } else if ( arr[mid] > target) { right = mid - 1 ; } else { left = mid + 1 ; } } return -1 ; }
分治算法
分治算法的核心就是分 和治 两个部分,即先把问题分解成一个一个小的问题,然后通过解决一个一个的小的问题,合成大的解,最后解决整个问题,这里使用汉诺塔问题作为示例
汉诺塔问题,经典问题,简单的来说就是,有A,B,C三根柱子,其中A柱子上面有n个圆盘,每次移动一个圆盘并且不能把大圆盘放在小圆盘的上面,问一共需要多少步能把圆盘全部移动到C柱子上。
实现思路
首先我们一步一步的逆着思考,要想把n个圆盘移动到C上,把上面的n-1个圆盘看作一个整体,我们就需要把n-1个圆盘移动到B上面,然后把第n个圆盘移动到C柱上,再将n-1移动到C柱上面,完成最后一步移动,再往前想,要移动n-1个圆盘到B柱上,就需要把n-2个圆盘先移动到C柱上,然后把第n-1个圆盘移动到B柱上,然后把第n-2个圆盘移动到B柱上,完成移动,再往前推,我们可以发现他们的实现过程都是类似的,直到n最后是1的时候就可以移动了,完全可以使用递归实现
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Hanoitower { public static void main (String[] args) { hanoiTower(3 , 'A' , 'B' , 'C' ); } public static void hanoiTower (int num, char a, char b, char c) { if (num == 1 ) { System.out.println("第1个盘从 " + a + "->" + c); } else { hanoiTower(num - 1 , a, c, b); System.out.println("第" + num + "个盘从 " + a + "->" + c); hanoiTower(num - 1 , b, a, c); } } }
动态规划
动态规划算法是我们的老朋友了,动态规划的算法的核心就是需要将以前的状态存储起来,然后再根据之前的状态推导出之后的状态,最后得到我们的最终答案,其中核心便是动态规划的动态转移方程。下面以背包问题举例说明
背包问题:
有一个背包,容量为4磅,现在有如下物品,要求使装入背包的总价值最大,并且重量不超出背包的最大容量,要求装入的物品不重复 这里的背包问题是01背包问题(01背包,每个物品最多放一个,完全背包问题则是每种物品都有无限件可以使用)
实现思路
利用动态规划,每次遍历到第i个物品,根据w[i] 和G[i] 来确定是否需要放入该物品,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 public class KnapsackProblem { public static void main (String[] args) { int [] w = {1 , 4 , 3 }; int [] val = {1500 , 3000 , 2000 }; int m = 4 ; int n = val.length; int [][] v = new int [n+1 ][m+1 ]; int [][] path = new int [n+1 ][m+1 ]; for (int i = 0 ; i < v.length; i++) { v[i][0 ] = 0 ; } for (int i=0 ; i < v[0 ].length; i++) { v[0 ][i] = 0 ; } for (int i = 1 ; i < v.length; i++) { for (int j=1 ; j < v[0 ].length; j++) { if (w[i-1 ]> j) { v[i][j]=v[i-1 ][j]; } else { if (v[i - 1 ][j] < val[i - 1 ] + v[i - 1 ][j - w[i - 1 ]]) { v[i][j] = val[i - 1 ] + v[i - 1 ][j - w[i - 1 ]]; path[i][j] = 1 ; } else { v[i][j] = v[i - 1 ][j]; } } } } for (int i =0 ; i < v.length;i++) { for (int j = 0 ; j < v[i].length;j++) { System.out.print(v[i][j] + " " ); } System.out.println(); } System.out.println("============================" ); int i = path.length - 1 ; int j = path[0 ].length - 1 ; while (i > 0 && j > 0 ) { if (path[i][j] == 1 ) { System.out.printf("第%d个商品放入到背包\n" , i); j -= w[i - 1 ]; } i--; } } }
KMP算法
字符串匹配问题,这个算法常用于在一个文本串S内查找一个模式串P的位置,字符串的匹配问题就是:有一个字符串str1 = “BBC ABCDAB ABCDABCDABDE”和一个子串 str2 = “ABCDABD” 现在要判断str1是否含有str2,如果存在,就返回第一次出现的位置,如果没有,就返回-1,KMP算法的核心就在于部分匹配值的应用
这个部分匹配值指的就是以这个节点前面的字符串为一个全新的字符串,他的前缀和后缀的重复长度,而KMP需要回溯移动的位数指的就是已匹配的字符数-对应的部分匹配值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public class KMPAlgorithm { public static void main (String[] args) { String str1 = "BBC ABCDAB ABCDABCDABDE" ; String str2 = "ABCDABD" ; int [] next = kmpNext("ABCDABD" ); System.out.println("next=" + Arrays.toString(next)); int index = kmpSearch(str1, str2, next); System.out.println("index=" + index); } public static int kmpSearch (String str1, String str2, int [] next) { for (int i = 0 , j = 0 ; i < str1.length(); i++) { while ( j > 0 && str1.charAt(i) != str2.charAt(j)) { j = next[j-1 ]; } if (str1.charAt(i) == str2.charAt(j)) { j++; } if (j == str2.length()) { return i - j + 1 ; } } return -1 ; } public static int [] kmpNext(String dest) { int [] next = new int [dest.length()]; next[0 ] = 0 ; for (int i = 1 , j = 0 ; i < dest.length(); i++) { while (j > 0 && dest.charAt(i) != dest.charAt(j)) { j = next[j-1 ]; } if (dest.charAt(i) == dest.charAt(j)) { j++; } next[i] = j; } return next; } }
贪心算法
贪心算法即在当前情况下寻找最优的那个解,因此最终得到的结果不一定是最优解 ,下面以集合覆盖问题为例,假设存在下面需要付费的广播台,以及广播台信号可以覆盖的地区,如何选择最少的广播台,让所有的地区都可以接收到信号
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 public class GreedyAlgorithm { public static void main (String[] args) { HashMap<String,HashSet<String>> broadcasts = new HashMap<String, HashSet<String>>(); HashSet<String> hashSet1 = new HashSet<String>(); hashSet1.add("北京" ); hashSet1.add("上海" ); hashSet1.add("天津" ); HashSet<String> hashSet2 = new HashSet<String>(); hashSet2.add("广州" ); hashSet2.add("北京" ); hashSet2.add("深圳" ); HashSet<String> hashSet3 = new HashSet<String>(); hashSet3.add("成都" ); hashSet3.add("上海" ); hashSet3.add("杭州" ); HashSet<String> hashSet4 = new HashSet<String>(); hashSet4.add("上海" ); hashSet4.add("天津" ); HashSet<String> hashSet5 = new HashSet<String>(); hashSet5.add("杭州" ); hashSet5.add("大连" ); broadcasts.put("K1" , hashSet1); broadcasts.put("K2" , hashSet2); broadcasts.put("K3" , hashSet3); broadcasts.put("K4" , hashSet4); broadcasts.put("K5" , hashSet5); HashSet<String> allAreas = new HashSet<String>(); allAreas.add("北京" ); allAreas.add("上海" ); allAreas.add("天津" ); allAreas.add("广州" ); allAreas.add("深圳" ); allAreas.add("成都" ); allAreas.add("杭州" ); allAreas.add("大连" ); ArrayList<String> selects = new ArrayList<String>(); HashSet<String> tempSet = new HashSet<String>(); String maxKey = null ; while (allAreas.size() != 0 ) { maxKey = null ; for (String key : broadcasts.keySet()) { tempSet.clear(); HashSet<String> areas = broadcasts.get(key); tempSet.addAll(areas); tempSet.retainAll(allAreas); if (tempSet.size() > 0 && (maxKey == null || tempSet.size() >broadcasts.get(maxKey).size())){ maxKey = key; } } if (maxKey != null ) { selects.add(maxKey); allAreas.removeAll(broadcasts.get(maxKey)); } } System.out.println("得到的选择结果是" + selects); } }
最小生成树算法
先例举一个问题,有下图所示的7个村庄,现在需要修路把7个村庄联通,问如何修路保证各个村庄都能联通,并且总的修建公路的总里程最短
这个问题的实质就是怎么生成最小生成树的问题,而解决这个问题的算法主要 有两个,分别从两个不同的方向对怎么生成最小生成树做出了解释
普里姆算法
普里姆算法主要看的是点,依次寻找该点到另一个点的最短路径是哪条,不断重复,最终找到最小生成树
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 public class PrimAlgorithm { public static void main (String[] args) { char [] data = new char []{'A' ,'B' ,'C' ,'D' ,'E' ,'F' ,'G' }; int verxs = data.length; int [][]weight=new int [][]{ {10000 ,5 ,7 ,10000 ,10000 ,10000 ,2 }, {5 ,10000 ,10000 ,9 ,10000 ,10000 ,3 }, {7 ,10000 ,10000 ,10000 ,8 ,10000 ,10000 }, {10000 ,9 ,10000 ,10000 ,10000 ,4 ,10000 }, {10000 ,10000 ,8 ,10000 ,10000 ,5 ,4 }, {10000 ,10000 ,10000 ,4 ,5 ,10000 ,6 }, {2 ,3 ,10000 ,10000 ,4 ,6 ,10000 },}; MGraph graph = new MGraph(verxs); MinTree minTree = new MinTree(); minTree.createGraph(graph, verxs, data, weight); minTree.showGraph(graph); minTree.prim(graph, 1 ); } }class MinTree { public void createGraph (MGraph graph, int verxs, char data[], int [][] weight) { int i, j; for (i = 0 ; i < verxs; i++) { graph.data[i] = data[i]; for (j = 0 ; j < verxs; j++) { graph.weight[i][j] = weight[i][j]; } } } public void showGraph (MGraph graph) { for (int [] link: graph.weight) { System.out.println(Arrays.toString(link)); } } public void prim (MGraph graph, int v) { int visited[] = new int [graph.verxs]; visited[v] = 1 ; int h1 = -1 ; int h2 = -1 ; int minWeight = 10000 ; for (int k = 1 ; k < graph.verxs; k++) { for (int i = 0 ; i < graph.verxs; i++) { for (int j = 0 ; j< graph.verxs;j++) { if (visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight) { minWeight = graph.weight[i][j]; h1 = i; h2 = j; } } } System.out.println("边<" + graph.data[h1] + "," + graph.data[h2] + "> 权值:" + minWeight); visited[h2] = 1 ; minWeight = 10000 ; } } }class MGraph { int verxs; char [] data; int [][] weight; public MGraph (int verxs) { this .verxs = verxs; data = new char [verxs]; weight = new int [verxs][verxs]; } }
克鲁斯卡尔算法
这个算法相比于上一个普里姆算法,主要突出的特点就是,克鲁斯卡尔算法是先将边排序,不断寻找最短的边,并一直判断这些边是否是联通的联通的就不行(判断的方法使用了并查集的思想),直到这些点全部都联通
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 public class KruskalCase { private int edgeNum; private char [] vertexs; private int [][] matrix; private static final int INF = Integer.MAX_VALUE; public static void main (String[] args) { char [] vertexs = {'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' }; int matrix[][] = { { 0 , 12 , INF, INF, INF, 16 , 14 }, { 12 , 0 , 10 , INF, INF, 7 , INF}, { INF, 10 , 0 , 3 , 5 , 6 , INF}, { INF, INF, 3 , 0 , 4 , INF, INF}, { INF, INF, 5 , 4 , 0 , 2 , 8 }, { 16 , 7 , 6 , INF, 2 , 0 , 9 }, { 14 , INF, INF, INF, 8 , 9 , 0 }}; KruskalCase kruskalCase = new KruskalCase(vertexs, matrix); kruskalCase.print(); kruskalCase.kruskal(); } public KruskalCase (char [] vertexs, int [][] matrix) { int vlen = vertexs.length; this .vertexs = new char [vlen]; for (int i = 0 ; i < vertexs.length; i++) { this .vertexs[i] = vertexs[i]; } this .matrix = new int [vlen][vlen]; for (int i = 0 ; i < vlen; i++) { for (int j= 0 ; j < vlen; j++) { this .matrix[i][j] = matrix[i][j]; } } for (int i =0 ; i < vlen; i++) { for (int j = i+1 ; j < vlen; j++) { if (this .matrix[i][j] != INF) { edgeNum++; } } } } public void kruskal () { int index = 0 ; int [] ends = new int [edgeNum]; EData[] rets = new EData[edgeNum]; EData[] edges = getEdges(); System.out.println("图的边的集合=" + Arrays.toString(edges) + " 共" + edges.length); sortEdges(edges); for (int i=0 ; i < edgeNum; i++) { int p1 = getPosition(edges[i].start); int p2 = getPosition(edges[i].end); int m = getEnd(ends, p1); int n = getEnd(ends, p2); if (m != n) { ends[m] = n; rets[index++] = edges[i]; } } System.out.println("最小生成树为" ); for (int i = 0 ; i < index; i++) { System.out.println(rets[i]); } } public void print () { System.out.println("邻接矩阵为: \n" ); for (int i = 0 ; i < vertexs.length; i++) { for (int j=0 ; j < vertexs.length; j++) { System.out.printf("%12d" , matrix[i][j]); } System.out.println(); } } private void sortEdges (EData[] edges) { for (int i = 0 ; i < edges.length - 1 ; i++) { for (int j = 0 ; j < edges.length - 1 - i; j++) { if (edges[j].weight > edges[j+1 ].weight) { EData tmp = edges[j]; edges[j] = edges[j+1 ]; edges[j+1 ] = tmp; } } } } private int getPosition (char ch) { for (int i = 0 ; i < vertexs.length; i++) { if (vertexs[i] == ch) { return i; } } return -1 ; } private EData[] getEdges() { int index = 0 ; EData[] edges = new EData[edgeNum]; for (int i = 0 ; i < vertexs.length; i++) { for (int j=i+1 ; j <vertexs.length; j++) { if (matrix[i][j] != INF) { edges[index++] = new EData(vertexs[i], vertexs[j], matrix[i][j]); } } } return edges; } private int getEnd (int [] ends, int i) { while (ends[i] != 0 ) { i = ends[i]; } return i; } }class EData { char start; char end; int weight; public EData (char start, char end, int weight) { this .start = start; this .end = end; this .weight = weight; } @Override public String toString () { return "EData [<" + start + ", " + end + ">= " + weight + "]" ; } }
最短路径算法
最短路径,故名思意,就是寻找从起点到终点的最短的路径,解决这个问题的,同样有两种算法,
迪杰斯特拉算法
这个算法的主要特点就是以起始点为中心向外层层扩展(就是广度优先搜索的思想),直到扩展到终点为止
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 public class DijkstraAlgorithm { public static void main (String[] args) { char [] vertex = { 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' }; int [][] matrix = new int [vertex.length][vertex.length]; final int N = 65535 ; matrix[0 ]=new int []{N,5 ,7 ,N,N,N,2 }; matrix[1 ]=new int []{5 ,N,N,9 ,N,N,3 }; matrix[2 ]=new int []{7 ,N,N,N,8 ,N,N}; matrix[3 ]=new int []{N,9 ,N,N,N,4 ,N}; matrix[4 ]=new int []{N,N,8 ,N,N,5 ,4 }; matrix[5 ]=new int []{N,N,N,4 ,5 ,N,6 }; matrix[6 ]=new int []{2 ,3 ,N,N,4 ,6 ,N}; Graph graph = new Graph(vertex, matrix); graph.showGraph(); graph.dsj(2 ); graph.showDijkstra(); } }class Graph { private char [] vertex; private int [][] matrix; private VisitedVertex vv; public Graph (char [] vertex, int [][] matrix) { this .vertex = vertex; this .matrix = matrix; } public void showDijkstra () { vv.show(); } public void showGraph () { for (int [] link : matrix) { System.out.println(Arrays.toString(link)); } } public void dsj (int index) { vv = new VisitedVertex(vertex.length, index); update(index); for (int j = 1 ; j <vertex.length; j++) { index = vv.updateArr(); update(index); } } private void update (int index) { int len = 0 ; for (int j = 0 ; j < matrix[index].length; j++) { len = vv.getDis(index) + matrix[index][j]; if (!vv.in(j) && len < vv.getDis(j)) { vv.updatePre(j, index); vv.updateDis(j, len); } } } }class VisitedVertex { public int [] already_arr; public int [] pre_visited; public int [] dis; public VisitedVertex (int length, int index) { this .already_arr = new int [length]; this .pre_visited = new int [length]; this .dis = new int [length]; Arrays.fill(dis, 65535 ); this .already_arr[index] = 1 ; this .dis[index] = 0 ; } public boolean in (int index) { return already_arr[index] == 1 ; } public void updateDis (int index, int len) { dis[index] = len; } public void updatePre (int pre, int index) { pre_visited[pre] = index; } public int getDis (int index) { return dis[index]; } public int updateArr () { int min = 65535 , index = 0 ; for (int i = 0 ; i < already_arr.length; i++) { if (already_arr[i] == 0 && dis[i] < min ) { min = dis[i]; index = i; } } already_arr[index] = 1 ; return index; } public void show () { System.out.println("==========================" ); for (int i : already_arr) { System.out.print(i + " " ); } System.out.println(); for (int i : pre_visited) { System.out.print(i + " " ); } System.out.println(); for (int i : dis) { System.out.print(i + " " ); } System.out.println(); char [] vertex = { 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' }; int count = 0 ; for (int i : dis) { if (i != 65535 ) { System.out.print(vertex[count] + "(" +i+") " ); } else { System.out.println("N " ); } count++; } System.out.println(); } }
弗洛伊德算法
弗洛伊德算法使用的是类似于上面的额动态规划的思想,不断刷新各个节点之间最短值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 public class FloydAlgorithm { public static void main (String[] args) { char [] vertex = { 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' }; int [][] matrix = new int [vertex.length][vertex.length]; final int N = 65535 ; matrix[0 ] = new int [] { 0 , 5 , 7 , N, N, N, 2 }; matrix[1 ] = new int [] { 5 , 0 , N, 9 , N, N, 3 }; matrix[2 ] = new int [] { 7 , N, 0 , N, 8 , N, N }; matrix[3 ] = new int [] { N, 9 , N, 0 , N, 4 , N }; matrix[4 ] = new int [] { N, N, 8 , N, 0 , 5 , 4 }; matrix[5 ] = new int [] { N, N, N, 4 , 5 , 0 , 6 }; matrix[6 ] = new int [] { 2 , 3 , N, N, 4 , 6 , 0 }; Graph graph = new Graph(vertex.length, matrix, vertex); graph.floyd(); graph.show(); } }class Graph { private char [] vertex; private int [][] dis; private int [][] pre; public Graph (int length, int [][] matrix, char [] vertex) { this .vertex = vertex; this .dis = matrix; this .pre = new int [length][length]; for (int i = 0 ; i < length; i++) { Arrays.fill(pre[i], i); } } public void show () { char [] vertex = { 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' }; for (int k = 0 ; k < dis.length; k++) { for (int i = 0 ; i < dis.length; i++) { System.out.print(vertex[pre[k][i]] + " " ); } System.out.println(); for (int i = 0 ; i < dis.length; i++) { System.out.print("(" +vertex[k]+"到" +vertex[i]+"的最短路径是" + dis[k][i] + ") " ); } System.out.println(); System.out.println(); } } public void floyd () { int len = 0 ; for (int k = 0 ; k < dis.length; k++) { for (int i = 0 ; i < dis.length; i++) { for (int j = 0 ; j < dis.length; j++) { len = dis[i][k] + dis[k][j]; if (len < dis[i][j]) { dis[i][j] = len; pre[i][j] = pre[k][j]; } } } } } }
马踏棋盘算法
将马随机放在国际象棋的8*8的某个方格中,按照马走日字的规则进行移动,每个方格只进入一次,走遍棋盘上的全部64个方格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 public class HorseChessboard { private static int X; private static int Y; private static boolean visited[]; private static boolean finished; public static void main (String[] args) { System.out.println("骑士周游算法,开始运行~~" ); X = 8 ; Y = 8 ; int row = 1 ; int column = 1 ; int [][] chessboard = new int [X][Y]; visited = new boolean [X * Y]; long start = System.currentTimeMillis(); traversalChessboard(chessboard, row - 1 , column - 1 , 1 ); long end = System.currentTimeMillis(); System.out.println("共耗时: " + (end - start) + " 毫秒" ); for (int [] rows : chessboard) { for (int step: rows) { System.out.print(step + "\t" ); } System.out.println(); } } public static void traversalChessboard (int [][] chessboard, int row, int column, int step) { chessboard[row][column] = step; visited[row * X + column] = true ; ArrayList<Point> ps = next(new Point(column, row)); sort(ps); while (!ps.isEmpty()) { Point p = ps.remove(0 ); if (!visited[p.y * X + p.x]) { traversalChessboard(chessboard, p.y, p.x, step + 1 ); } } if (step < X * Y && !finished ) { chessboard[row][column] = 0 ; visited[row * X + column] = false ; } else { finished = true ; } } public static ArrayList<Point> next (Point curPoint) { ArrayList<Point> ps = new ArrayList<Point>(); Point p1 = new Point(); if ((p1.x = curPoint.x - 2 ) >= 0 && (p1.y = curPoint.y -1 ) >= 0 ) { ps.add(new Point(p1)); } if ((p1.x = curPoint.x - 1 ) >=0 && (p1.y=curPoint.y-2 )>=0 ) { ps.add(new Point(p1)); } if ((p1.x = curPoint.x + 1 ) < X && (p1.y = curPoint.y - 2 ) >= 0 ) { ps.add(new Point(p1)); } if ((p1.x = curPoint.x + 2 ) < X && (p1.y = curPoint.y - 1 ) >= 0 ) { ps.add(new Point(p1)); } if ((p1.x = curPoint.x + 2 ) < X && (p1.y = curPoint.y + 1 ) < Y) { ps.add(new Point(p1)); } if ((p1.x = curPoint.x + 1 ) < X && (p1.y = curPoint.y + 2 ) < Y) { ps.add(new Point(p1)); } if ((p1.x = curPoint.x - 1 ) >= 0 && (p1.y = curPoint.y + 2 ) < Y) { ps.add(new Point(p1)); } if ((p1.x = curPoint.x - 2 ) >= 0 && (p1.y = curPoint.y + 1 ) < Y) { ps.add(new Point(p1)); } return ps; } public static void sort (ArrayList<Point> ps) { ps.sort(new Comparator<Point>() { @Override public int compare (Point o1, Point o2) { int count1 = next(o1).size(); int count2 = next(o2).size(); if (count1 < count2) { return -1 ; } else if (count1 == count2) { return 0 ; } else { return 1 ; } } }); } }
11. 并查集
//todo