数据结构与算法 1 线性存储与非线性存储 1.1 线性结构
线性结构 :特点数据元素直接存在一对一的线性关系
线性结构有两种不同的存储结构,顺序存储(数组)和链式存储结构(链表)。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的。
链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存方的数据元素以及相邻元素的地址信息。
线性结构常见的有:数组,队列,链表和栈。
1.2 非线性结构 非线性结构:二维数组,多维数组,广义表,树结构,图结构。
2 数组 2.1 稀疏数组 当一个数组中大部分元素为0的时候,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组处理的方式:
记录数组一共有几行几列,有多少个不同 的值。
把具有不同的元素的行列及值记录在一个小规模数组中,从而缩小程序的规模。
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 public class 稀疏数组 { public static void main (String[] args) { int chessArr1[][] = new int [11 ][11 ]; chessArr1[1 ][2 ] =1 ; chessArr1[2 ][3 ] = 2 ; System.out.println("原始的二维为:" ); for (int [] row : chessArr1) { for (int data: row) { System.out.printf("%d " ,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 ]); } 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 " ,data); } System.out.println(); } } }
3 队列 3.1 队列
队列是一有序列表,可以使用数组或者是链表来实现.
遵循先入先从的原则。
==一般我们采用rear 指针和front指针进行操作。==
3.2队列的模拟 队列是有序列表,使用数组结构来存储队列的数据。
队列的入队一般使用addQueue,addQueue的处理需要两个步骤。
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 public class ArrayQueueDemo { public static void main (String[] args) { ArrayQueue aq=new ArrayQueue (3 ); char key = ' ' ; Scanner sc = new Scanner (System.in); boolean loop = true ; while (loop) { System.out.println("s(show):显示队列" ); System.out.println("e(exit):退出队列" ); System.out.println("a(add):添加数据到队列" ); System.out.println("g(get):从队列取出数据" ); System.out.println("h(head):查看队列头的数据" ); key = sc.next().charAt(0 ); switch (key) { case 's' : aq.showQueue(); break ; case 'a' : System.out.println("请输入一个数" ); int value = sc.nextInt(); aq.addQueue(value); break ; case 'g' : try { int res = aq.getQueue(); System.out.printf("取出的数据实%d\n" ,res); aq.getQueue(); } catch (Exception e) { System.out.println(e.getMessage()); } break ; case 'h' : try { int res = aq.headQueue(); System.out.printf("取出的数据实%d\n" ,res); } catch (Exception e) { System.out.println(e.getMessage()); } case 'e' : sc.close(); loop = false ; break ; default : break ; } } System.out.println("程序退出" ); } } 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 isNull () { return rear == front; } public void addQueue (int n) { if (isFull()) { System.out.println("队列满,不能加入数据" ); return ; } rear ++ ; arr[rear] = n; } public int getQueue () { if (isNull()) { throw new RuntimeException ("队列为空,不能获取数据" ); } front ++; return arr[front]; } public void showQueue () { if (isNull()) { 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 (isNull()) { throw new RuntimeException ("队列空,没有数据" ); } return arr[front + 1 ]; } }
此处存在一个问题,就是我们只能使用一次,无法对队列进行多次使用。
将这个数组使用算法改变成为一个环形队列 方式,采用取模的方式。
3.3 判断队列为空队列为满 ==front == rear 代表队列为空。==
当指针rear 小于队列的最大下标maxSize - 1,则将数据存入rear锁指向的数组元素中,否则无法存入数据
==rear == maxSize -1 代表队列已满==
3.4 使用数组模拟环形队列 思路:
front 变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素。
front的初始值为0;
rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为约定。rear 的初始值为0;
当队列满时,条件是(rear + 1)%maxSize == front
此时满。
当队列为空的条件,rear == front 空
当这样分析后,此时队列中有效的数据的个数是 (rear + maxSize - front)%maxSize //rear=1 front=0
以上我们就可以按照我们的思路对原来的代码进行修改。
4. 链表 链表是有序的列表:
链表是以节点 的方式来存储的,是一种链式存储的 方法存储。
每个节点包含data域,next域 ,指向下一个节点。
链表的各个节点不一定是连续存储
链表分带头节点的链表和不带头结点 的链表,根据实际的需求来确定。
4.1 单向链表 head 节点
单向链表的实现
不存放具体的数据
作用就是表示单链表头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 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(hero2); singleLinkedList.add(hero3); singleLinkedList.add(hero4); singleLinkedList.list(); } } class SingleLinkedList { private HeroNode head = new HeroNode (0 ,"" ,"" ); public void add (HeroNode heroNode) { HeroNode temp = head; while (true ) { if (temp.next == null ) { break ; } temp = temp.next; } temp.next = heroNode; } 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) { super (); this .no = no; this .name = name; this .nickName = nickName; } @Override public String toString () { return "HeroNode [no=" + no + ", name=" + name + ", nickName=" + nickName + "]" ; } public HeroNode () { super (); } }
目前,我们需要添加相关节点的进行有序添加。
有序添加 需要按照编号的顺序添加:
首先找到新天节点的节点的位置,是通过辅助变量(指针),通过遍历来搞定
新的节点 next=temp.next
将temp.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 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.addByOrder(hero3); singleLinkedList.addByOrder(hero1); singleLinkedList.addByOrder(hero4); singleLinkedList.addByOrder(hero2); singleLinkedList.addByOrder(hero2); singleLinkedList.list(); } } class SingleLinkedList { private HeroNode head = new HeroNode (0 ,"" ,"" ); 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 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) { super (); this .no = no; this .name = name; this .nickName = nickName; } @Override public String toString () { return "HeroNode [no=" + no + ", name=" + name + ", nickName=" + nickName + "]" ; } public HeroNode () { super (); } }
修改链表中的参数
先找到该节点,通过遍历。
temp.name = newHeroNode.name;temp.nickName=newHeroNode.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 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 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.addByOrder(hero3); singleLinkedList.addByOrder(hero1); singleLinkedList.addByOrder(hero4); singleLinkedList.addByOrder(hero2); singleLinkedList.addByOrder(hero2); System.out.println("修改之前的" ); singleLinkedList.list(); HeroNode newHeroNode = new HeroNode (2 ,"小卢" ,"玉麒麟···" ); singleLinkedList.update(newHeroNode); System.out.println("修改之后的的链表情况" ); singleLinkedList.list(); } } class SingleLinkedList { private HeroNode head = new HeroNode (0 ,"" ,"" ); 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("链表为空。。。" ); } 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 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) { super (); this .no = no; this .name = name; this .nickName = nickName; } @Override public String toString () { return "HeroNode [no=" + no + ", name=" + name + ", nickName=" + nickName + "]" ; } public HeroNode () { super (); } }
删除节点 从单链表中删除一个节点的思路
删除节点之前,我们需要将有关节点先找到需要删除的这个节点的前一个节点temp
temp.next = temp.next.next;
被删除的节点将不会有其他的指向,会被JVM垃圾回收机制回收。
代码如下:
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 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.addByOrder(hero3); singleLinkedList.addByOrder(hero1); singleLinkedList.addByOrder(hero4); singleLinkedList.addByOrder(hero2); singleLinkedList.addByOrder(hero2); System.out.println("修改之前的" ); singleLinkedList.list(); HeroNode newHeroNode = new HeroNode (2 ,"小卢" ,"玉麒麟···" ); singleLinkedList.update(newHeroNode); System.out.println("修改之后的的链表情况" ); singleLinkedList.list(); singleLinkedList.del(4 ); singleLinkedList.del(1 ); singleLinkedList.del(1 ); System.out.println("删除后的链表情况。。。" ); singleLinkedList.list(); } } class SingleLinkedList { private HeroNode head = new HeroNode (0 ,"" ,"" ); 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("链表为空。。。" ); } 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) { super (); this .no = no; this .name = name; this .nickName = nickName; } @Override public String toString () { return "HeroNode [no=" + no + ", name=" + name + ", nickName=" + nickName + "]" ; } public HeroNode () { super (); } }
链表面试题: 面试题,获取单链表的节点个数(如果是带头结点的链表,需求不统计头结点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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; } main: System.out.println("有效的节点个数为: " +getLength(singleLinkedList.getHead()));
查找单链表中的倒数第k个节点【新浪面试题】
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 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; } main: HeroNode res = findLastIndexNode(singleLinkedList.getHead(), 1 ); System.out.println("res=" + res);
单链表的反转【腾讯面试,难度适中】
先定义一个节点reverseHead = new HeroNodeHead
的最前端。
从头到尾便利原来的指针,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端。
原来的链表head.next = reverseHead.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 public static void reverseList (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; } main: System.out.println("此时这个时候是原先链表" ); singleLinkedList.list(); System.out.println("反转后的单链表" ); reverseList(singleLinkedList.getHead()); singleLinkedList.list();
从尾到头打印单链表 【方式1,反向遍历,方式2:stack栈】
思路:
上面的题要求就是逆序打印单链表。
方式1:先将单链表进行反转,然后在遍历就行,但是这样会破坏原来的单链表的结构,不可取 。
方式2:可以利用栈这个数据结构,将各个节点压入栈中,然后利用栈的先进后出 的特点,实现逆序打印的效果。此方式不会影响链表的结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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()); } } main: System.out.println("逆序打印单链表,此时没有改变链表的结构。。。" ); reversePrint(singleLinkedList.getHead());
合并两个有序的单链表,合并之后的链表仍然井然有序【习题】
单链表存在的问题:
单链表不能实现自我删除,其查找的方向只能是一个方向而双向链表可以向前或者向后查找
单向链表不能自我删除,需要靠辅助节点,而双向链表,可以自我删除,所以前面我们单链表删除时节点,总是找到temp的下一个节点来删除。
4.2 双向链表 双向链表:
所谓双向链表
通俗的讲就是可以从两端进行查找。
分析双向链表的遍历,添加,修改,删除的操作思路
遍历的方式和单链表一样,只是可以向前查找,也可以向后查找。
添加(默认添加搭配双向链表的最后)
先找到双向链表的最后这个节点
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 175 176 177 178 public class DoubleLinkListDemo { public static void main (String[] args) { System.out.println("双向链表的测试:" ); HeroNode2 hero1 = new HeroNode2 (1 ,"张三" ,"黑脸麻瓜" ); HeroNode2 hero2 = new HeroNode2 (2 ,"李四" ,"红脸堇瓜" ); HeroNode2 hero3 = new HeroNode2 (3 ,"王五" ,"白脸冬瓜" ); HeroNode2 hero4 = new HeroNode2 (4 ,"赵六" ,"绿脸西瓜" ); DoubleLinkList doubleLinkList = new DoubleLinkList (); doubleLinkList.add(hero1); doubleLinkList.add(hero2); doubleLinkList.add(hero3); doubleLinkList.add(hero4); doubleLinkList.list(); HeroNode2 newHeroNode = new HeroNode2 (2 ,"得力" ,"自研菲亚" ); doubleLinkList.update(newHeroNode); System.out.println("修改后的结果:" ); doubleLinkList.list(); doubleLinkList.del(2 ); System.out.println("删除后的结果:" ); doubleLinkList.list(); } } class DoubleLinkList { 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("链表为空。。。" ); } 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("链表为空,无法删除" ); } 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) { super (); this .no = no; this .name = name; this .nickName = nickName; } public HeroNode2 () { super (); } @Override public String toString () { return "HeroNode2 [no=" + no + ", name=" + name + ", nickName=" + nickName + "]" ; } }
习题:
双向链表的第二种添加方式,按照编号顺序(提示,按照单链表的顺序进行添加即可,稍作修改);
4.3 单向环形链表: 约瑟夫问题:设编号为1,2,….n,的n个人围坐在一圈,约定编号为k(1<= k <=m)的人从1开始报数,数到从1开始报数,数到m的那个人出列,他的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
n = 5,即有5个人
k =1,从第一个人开始报数
m=2,数2下。
思路:
先创建第一个节点,让first指向该节点,并形成环形
后面当我们没创建一个新的节点,就将该节点加入到已有的环形链表即可。
遍历环形链表:
先让一个辅助指针(变量) curBoy,指向first节点
然后通过一个while循环该环形链表即可,curBoy.next == first;
根据用户的输入,生成一个小孩出圈的顺序,事先应该指向环形链表的最后这个节点。
补充:小孩报数之前,先让first和helper移动 k -1 次
当小孩报数时,让first和helper指针同时移动m -1次
这个时候就可以将first指向的小孩节点出圈;
first = first.next
helper.next =first;
原来first指向的节点就没有任何引用,就会被垃圾回收机制回收
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 public class Josepfu { public static void main (String[] args) { CircleSinglelinkedList circleSinglelinkedList = new CircleSinglelinkedList (); circleSinglelinkedList.addBoy(10 ); circleSinglelinkedList.showBoy(); circleSinglelinkedList.countBoy(1 , 2 , 10 ); } } class CircleSinglelinkedList { private Boy first = new Boy (-1 ); 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) { super (); 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; } @Override public String toString () { return "Boy [no=" + no + ", next=" + next + "]" ; } }
5.栈
栈的英文Stack
栈是一种先入后出 (FILO - First in Last Out)的有序列表
栈是限制线性表中元素的插入和删除只能在线性表的同一端 进行特殊线性表。允许插入和删除的一端,为变化的一端称为栈顶 (Top),另一端为固定的一端,称为栈底 (Bottom);
根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最后放入的元素最后删除。
入栈push ,出栈pop 。
子程序的调用:在跳往子程序前,会先加个下一个指令的地址存放到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
处理递归调用,所谓递归,就是方法自己调用自己本身。与子程序的调用类似,只是除了存储下一个指令的地址外,也将参数,区域变量等数据存入堆栈中。
表达式的转换[中缀表达式转后缀表达式]与求值。
二叉树的变量
图形的深度优先(depth-first)搜索法。
5.1 栈的模拟实现 实现栈的思路:
使用数组来模拟栈
定义一个top来模拟栈顶,初始化为 -1;
入栈的操作,当有数据加入栈时,top++ ; stack[top] = data;
出栈的操作,int value = stack[top]; top -- ;return value;
反正有错误,bug一直都有。
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 public class ArrayStackDemo { public static void main (String[] args) { ArrayStack stack = new ArrayStack (4 ); String key = "" ; boolean loop = true ; Scanner sc = new Scanner (System.in); while (loop) { System.out.println("show:表示展示栈" ); System.out.println("exit:表示退出栈" ); System.out.println("push:表示添加数据到栈" ); System.out.println("pop:表示从栈取出数据" ); System.out.println("请输入你的操作" ); key = sc.next(); switch (key) { case "show" : stack.list(); break ; case "push" : System.out.println("请输入一个数" ); int value = sc.nextInt(); stack.push(value); break ; case "pop" : try { int res = stack.pop(); System.out.printf("出栈的数据是%d\n" ,res); } catch (Exception e) { System.out.println(e.getMessage()); } break ; case "exit" : sc.close(); loop = false ; break ; default : break ; } } System.out.println("程序退出了" ); } } 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()) { System.out.println("栈空,没有数据···" ); return ; } for (int i = top; i >= 0 ; i--) { System.out.printf("stack[%d]=%d\n" ,i,stack[i]); } } }
5.2 使用栈实现计算机 试题: 使用栈完成表达式7 2 2 - 5 + 1 - 5 + 3 - 4 =?
思路:
通过一个index值(索引).来变量我们的表达式
如果我们发现是一个数字就直接入栈
如果发现扫描到的是一个符号,就分如下情况进行分析
如果发现当前符号为空,则直接入栈
如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符。这个时候就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符插入符号栈
如果当前符号栈中有操作符,就进行比较,如果当前操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,在从符号栈中pop一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈,如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈。
当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行。
最后在数栈只有一个数子,就是表达式的结果。
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 public class Calculator1 { public static void main (String[] args) { String expression = "3-2*1+1" ; 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 ArrayStack3 { private int maxSize; private int [] stack; private int top = -1 ; public ArrayStack3 (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; } }
5.3 前缀、中缀、后缀表达式(逆波兰表达式) 从右往左扫描表达式,遇到数子时,将数字压入栈中,遇到运算符时,弹出栈顶的两个数,用运算符对他们做相应的计算。一般情况中缀表达式是我们人为确定的表达式,但是计算机无法识别,故而我们需要将中缀表达式调整为后缀表达式。
从左到右表达式最右端,最后运算得出的值即为表达式的结果。
* 思路:
* 1.从左到右扫描,将3和4压入栈中
* 2.遇到 + 运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得到7,在将7压入栈中
* 3.将5入栈
* 4.接下来是*运算符,因此弹出5和7,计算7*5=35,将35入栈。
* 5.将6入栈
* 6.最后是 - 运算符,计算出35 -6 的值,即29,由此得出最终结果
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 逆波兰表达式 { public static void main (String[] args) { String suffixExpression = "15 4 + 5 * 6 -" ; List<String> rpnList = getListString(suffixExpression); System.out.println("逆波兰表达式:" + rpnList); int res = calculate(rpnList); System.out.println("计算的结果为:" + res); } 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 (); 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()); } }
5.4 中缀表达式转换为后缀表达式 实际开发中,我们需要将中缀表达式转换为后缀表达式
中缀表达式转后缀表达式的思路步骤
初始化两个栈,运算符s1和存储中间结果的栈s2
从左到右扫描中缀表达式;
遇到操作运算符时,将其压s2;
遇到运算符时,比较其与s1栈顶运算符的优先级
如果s1为空,或栈顶运算符为左括号“(”,则直接将次运算符入栈;
否则,若优先级比栈顶运算符的高,也将运算符压入s1
否则将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;
遇到括号
如果是左括号“(”,则直接压入s1
如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃。
重复步骤2至5,直到表达式的最右边
将s1中剩余的运算符依次弹出并压入s2
依次弹出s2中元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。
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 逆波兰表达式 { public static void main (String[] args) { String suffixExpression = "15 4 + 5 * 6 -" ; List<String> rpnList = getListString(suffixExpression); System.out.println("逆波兰表达式:" + rpnList); int res = calculate(rpnList); System.out.println("计算的结果为:" + res); System.out.println("=========================以上是中缀表达式====================" ); String experssion = "1+((2+3)*4)-5" ; List<String> infixExpressionList = toInfixExpressionList(experssion); System.out.println("中缀表达式对应的List " + infixExpressionList); List<String> parseSuffixExpressionList = parseSuffixExpressionList(infixExpressionList); System.out.println("后缀表达式对应的List" + parseSuffixExpressionList); System.out.printf("expression=%d" ,calculate(parseSuffixExpressionList)); } public static List<String> parseSuffixExpressionList (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> 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> 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 (); 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 : System.out.println("不存在该运算符" ); break ; } return result; } }
6.递归
递归就是方法自己调用自己
递归调用规则:
当程序执行到一个方法时,就会开辟一个独立的空间(栈)
方法的局部变量是独立的,不会相互影响,比如n变量
如果方法中使用的是引用类型变量(比如说数组),就会共享该引用类型的数据
递归必须向退出递归的条件逼近,否则就是无限递归,出现栈溢出也就是StackOverFlowError
。
当一个方法执行完毕,或者遇到return,就会返回,遵循谁调用,就将结果返回给谁,同时当方法执行完毕后返回时,该方法也就执行完毕。
6.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 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 ; System.out.println("当前地图为:" ); for (int i = 0 ; i < 8 ; i ++) { for (int j = 0 ;j < 7 ; j++) { System.out.print(map[i][j] + " " ); } System.out.println(); } setWay1(map, 1 , 1 ); System.out.println("小球改变方式,并标识:" ); 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 ; }else { 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 ; } } } public static boolean setWay1 (int [][] map,int i , int j) { if (map[6 ][5 ] == 2 ) { return true ; }else { if (map[i][j] == 0 ) { map[i][j] = 2 ; if (setWay1(map, i - 1 ,j)) { return true ; }else if (setWay1(map, i, j + 1 )) { return true ; }else if (setWay1(map, i + 1 , j)) { return true ; }else if (setWay1(map, i, j -1 )) { return true ; }else { map[i][j] = 3 ; return false ; } }else { return false ; } } } }
6.2 八皇后问题递归思路和实现 八皇后问题算法思路分析:
1)第一个皇后先放第一行第一列
2)第二个皇后放在第二行第一列、然后判断是否OK,如果不OK,继续放在第二列、第三列、依次把所有列都放完,找到一个合适
3)继续第三个皇后,还是第一列、第二列…….直到第8个皇后也能放在一个不冲突的位置,算是找到了一个正确解
4)当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解,全部得到.
5)然后回头继续第一个皇后放第二列,后面继续循环执行1,2,3,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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 public class EightQueenQuestions { int max = 8 ; int [] array = new int [max]; static int count = 0 ; public static void main (String[] args) { EightQueenQuestions queue8 = new EightQueenQuestions (); System.out.println("哪行哪列代表数字," + "比如0 4 7 5 2 6 1 3," + "0表示第0行0列," +"4表示第1行4列," +"5表示第2行6列," +"以此类推。" ); queue8.check(0 ); System.out.printf("共计有%d种解法" ,count); } private void check (int n) { if (n == max ) { print(); return ; } for (int i = 0 ; i < max; i ++) { array[n] = i; if (judge(n)) { check(n+1 ); } } } private 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 ; } private void print () { count ++; for (int i = 0 ; i < array.length; i++) { System.out.print(array[i] + " " ); } System.out.println(); System.out.println("===============" ); } }
7. 排序 7.1 8种基本排序
排序也称为排序算法,排序是一组数据,按照指定的顺序进行排列的过程。
排序的分类:
内部排序:将需要处理的所有数据都加载到内部存储器中进行排序
外部排序:数据量过大,无法全部加载到内存中,需要借助外部存储进行排序
常见的排序算法的分类:
内部排序:
插入排序:
直接插入排序
希尔排序
选择排序
简单选择排序
堆排序
交换排序
冒泡排序
快速排序
归并排序
基数排序
外部排序:内存和外存排序
7.2时间复杂度 算法的时间复杂度
一般我们度量一个程序执行时间的两种方法
事后统计:这种方法可行,但是存在两个问题,一是想要对设计的算法的运行性能进行评测,需要实际运行该程序,二是所得时间的统计量依赖于计算机的硬件,软件等环境因素,这种方式要求在同一台计算机的相同状态下运行,才能比较那个算法速度更快。
事前估算法:通过分析某个算法的时间复杂度来判断哪个算法更优。
1)一般情况下,算法中的基本操作语句的重复执行次数是问题规模n的某个函数,用f(n)表示,若有某个辅助函数f(n),便得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为算法的渐进间复杂度,简称时间复杂度。
2)T(n)不同,但时间复杂度可能相同。如:T(n)=n^2+7n+6与T(n)=3n^2+2n+2它们的(n)不同,但时间复杂度相同,都为O(n^2)。
3)计算时间复杂度的方法:
用常数1代替运行时间中的所有加法常数 T(n) = n^2 + 7n + 6; =>T(n) = n^2 + 7n + 1
修改后的运行次数函数中,只保留最高阶项
T(n) = n^2 + 7n + 1 => T(n) = n^2
去除最高阶项的系数T(n) =3n^2 =>O(n^2)
常数阶O(1)
1 2 3 4 5 int i = 1 ;int j = 2 ;++ i; j++ ; int m = i + j;
对数阶O(log2n)
1 2 3 4 int i = 1 ;while (i<n){ i=i*2 ; }
线性阶O(n)
1 2 3 4 for (int i = 1 ; i <= n ; ++ i){ j = i; j ++; }
线性对数阶O(nlog2n)
1 2 3 4 5 6 for (int i = 1 ; m < n; m++ ){ i = 1 ; while (i < n){ i *= 2 ; } }
平方阶O(n^2)
1 2 3 4 5 6 for ( int i = 0 ; i<= n; i ++){ for (int j = 0 ; j <= n; j++){ j = i; j ++ ; } }
立方阶O(n^3)
1 2 3 4 5 6 7 for (){ for (){ for (){ } } }
k次方阶O(n^k)
1 2 3 4 5 6 7 n层for (){ for (){ for (){ ...... } } }
指数阶O(2^n)
依次往下时间复杂度增长
排序法
平均时间
最差情况
稳定度
空间复杂度
备注
冒泡
O(n^2)
O(n^2)
稳定
1
n小比较好
交换
O(n^2)
O(n^2)
不稳定
1
n小比较好
选择
O(n^2)
O(n^2)
不稳定
1
n小比较好
插入
O(n^2)
O(n^2)
稳定
1
大部分已排序的较好
基数
O(logRB)
O(logRB)
稳定
1
B是真数(0-9),R是基数(个十百)
shell
O(nlogn)
O(n^s) 1<s<2
不稳定
1
s是所选分组
快速
O(nlogn)
O(n^2)
不稳定
O(nlogn)
n大时较好
归并
O(nlogn)
O(nlogn)
稳定
O(1)
n大时较好
堆
O(nlogn)
O(nlogn)
不稳定
O(1)
n大时候较好
7.3 排序算法的实现与设计 7.3.1 冒泡排序 思想:
冒泡排序通过对排序序列从前往后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大大元素慢慢从前往后移,就像水底气泡一样慢慢往上冒。
优化:
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志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 49 50 51 52 53 54 55 56 57 58 59 60 public class BubbleSort { public static void main (String[] args) { int arr[] = {3 ,9 ,-1 ,10 ,-2 }; int temp = 0 ; for (int i = 0 ; i < arr.length -1 ; i ++ ) { for (int j = 0 ; j < arr.length -1 -i; j ++) { if (arr[j]>arr[j+1 ]) { temp = arr[j]; arr[j] = arr[j+1 ]; arr[j+1 ] = temp; } } } System.out.println(Arrays.toString(arr)); } }
冒泡排序的优化:
因为在排序过程中
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 BubbleSortOptimize { public static void main (String[] args) { int [] arr = new int [80000 ]; for (int i = 0 ; i < 80000 ; i ++) { arr[i] = (int )(Math.random() * 80000 ); } Date date1 = new Date (); SimpleDateFormat simpleDateFormat = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); String dateStr = simpleDateFormat.format(date1); System.out.println("排序前的时间为=" + dateStr); bubbleSort(arr); Date date2 = new Date (); String dateStr2 = simpleDateFormat.format(date2); System.out.println("输出排序后的时间" + dateStr2); } public static void bubbleSort (int [] arr) { int temp = 0 ; boolean flag = false ; for (int i = 0 ; i < arr.length -1 ; i ++ ) { for (int j = 0 ; j < arr.length -1 -i; j ++) { if (arr[j]>arr[j+1 ]) { flag = true ; temp = arr[j]; arr[j] = arr[j+1 ]; arr[j+1 ] = temp; } } if (flag == false ) { break ; }else { flag = false ; } } } }
核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 int temp = 0 ; for (int i = 0 ; i < arr.length -1 ; i ++ ) { for (int j = 0 ; j < arr.length -1 -i; j ++) { if (arr[j]>arr[j+1 ]) { temp = arr[j]; arr[j] = arr[j+1 ]; arr[j+1 ] = temp; } } } System.out.println(Arrays.toString(arr));
7.3.2 选择排序 选择排序也是一种简单的排序方式,其基本思想在于:第一次从数组(arr[0]-arr[n-1])中选择最小的值,然后与数组第一个元素(arr[0])进行交换,第二次从arr[1]-arr[n-1
]中选取最小值,与arr[1]交换,……,知道将数组排序好,总共通过n-1
次,得到一个按照排序码从小到大的有序序列。
思路:
原始数组 : 101,34,119,1;
第一轮排序:1,34,119,101;
第二轮排序:1,34,119,101;
第三轮排序:1,34,101,119;
选则排序一共有数组大小-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 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 public class SelectSort { public static void main (String[] args) { int [] arr = new int [80000 ]; for (int i = 0 ; i < 80000 ; i ++) { arr[i] = (int )(Math.random() * 80000 ); } Date date1 = new Date (); SimpleDateFormat simpleDateFormat = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); String dateStr = simpleDateFormat.format(date1); System.out.println("排序前的时间为=" + dateStr); selectSort(arr); Date date2 = new Date (); String dateStr2 = simpleDateFormat.format(date2); System.out.println("输出排序后的时间" + dateStr2); } public static void selectSort (int [] arr) { for (int i = 0 ; i < arr.length ; i ++ ) { int minIndex = i; int min = arr[i]; for (int j = i +1 ; j < arr.length ; j++) { if (min > arr[j]) { min = arr[j]; minIndex = j ; } } if (minIndex != i) { arr[minIndex] = arr[i]; arr[i] = min; } } } }
对比冒泡排序发现,选择排序速度比冒泡排序快。
核心代码层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void selectSort (int [] arr) { for (int i = 0 ; i < arr.length ; i ++ ) { int minIndex = i; int min = arr[i]; for (int j = i +1 ; j < arr.length ; j++) { if (min > arr[j]) { min = arr[j]; minIndex = j ; } } if (minIndex != i) { arr[minIndex] = arr[i]; arr[i] = min; } }
7.3.3 插入排序 插入排序的基本思想: 把n个待排序的元素看成一个有序表和一个无序表,开始时,有序表只包含一个元素,无序表中包含有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 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 public class InsertSort { public static void main (String[] args) { int arr [] = {101 ,34 ,-1 ,119 ,1 ,89 }; insertSort(arr); } public static void insertSort (int [] arr) { for (int i = 1 ; i < arr.length; i++) { int insertVal = arr[i]; int insertIndex = i - 1 ; while (insertIndex >= 0 && insertVal < arr[insertIndex]) { arr[insertIndex + 1 ] = arr[insertIndex]; insertIndex -- ; } arr[insertIndex + 1 ] = insertVal; System.out.println("第" +i+"轮" ); System.out.println(Arrays.toString(arr)); } } }
核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 for (int i = 1 ; i < arr.length; i++) { int insertVal = arr[i]; int insertIndex = i - 1 ; while (insertIndex >= 0 && insertVal < arr[insertIndex]) { arr[insertIndex + 1 ] = arr[insertIndex]; insertIndex -- ; } if (insertIndex + 1 != i) { arr[insertIndex + 1 ] = insertVal; } }
通过时间比较,我们发现插入排序比选择排序时间更短,也就是说插入排序的效率高于选择排序。
缺点: 当插入的数是最小的数时,后移的次数明显增多,对效率有影响。
7.3.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 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 ShellSort { public static void main (String[] args) { int []arr = {8 ,9 ,1 ,7 ,2 ,3 ,5 ,4 ,6 ,0 }; shellSort(arr); } public static void shellSort (int [] arr) { int temp = 0 ; int count = 0 ; for (int gap = arr.length / 2 ; gap > 0 ;gap /=2 ) { for (int i = gap; i < arr.length ; i++) { for (int j = i -gap ; j >= 0 ; j -= gap) { if (arr[j] > arr[j + gap]) { temp = arr[j]; arr[j] = arr[j + gap]; arr[j + gap] = temp; } } } System.out.println("希尔排序后的第" +(++count)+"轮结果为:" + Arrays.toString(arr)); } } }
第二种方式移位法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static void shellSort2 (int [] arr) { for (int gap = arr.length / 2 ; gap > 0 ; gap /= 2 ) { for (int i = gap ; i < arr.length; i++) { int j = i; int temp = arr[j]; if (arr[j] < arr[j - gap]) { while (j - gap >= 0 && temp < arr[j - gap]) { arr[j] = arr[j-gap]; j -= gap; } arr[j] = temp; } } } }
使用移位法后,我们发现时间快了很多倍。
7.3.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 public class QuickSort { public static void main (String[] args) { int [] arr = new int [80000 ]; for (int i = 0 ; i < 80000 ; i ++) { arr[i] = (int )(Math.random() * 80000 ); } Date date1 = new Date (); SimpleDateFormat simpleDateFormat = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); String dateStr = simpleDateFormat.format(date1); System.out.println("排序前的时间为=" + dateStr); quickSort(arr, 0 ,arr.length - 1 ); Date date2 = new Date (); String dateStr2 = simpleDateFormat.format(date2); System.out.println("输出排序后的时间" + dateStr2); } public static void quickSort (int [] arr ,int left,int right) { int l = left; int r = right ; int temp = 0 ; int pivot = arr[(left + right) /2 ]; while (l < r) { while (arr[l] < pivot) { l += 1 ; } while (arr[r] > pivot) { r -= 1 ; } if ( l >= r) { break ; } temp = arr[l]; arr[l] = arr[r]; arr[r] = temp; if (arr[l] == pivot) { r -= 1 ; } if (arr[r] == pivot) { l += 1 ; } } if (l == r) { l += 1 ; r -= 1 ; } if (left < r) { quickSort(arr, left, r); } if (right > l) { quickSort(arr, l, right); } } }
7.3.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 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) { System.out.println(); 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)); } 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); merget(arr, left, mid, right, temp); } } public static void merget (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; System.out.println("tempLeft=" + tempLeft + ",right=" + right); while (tempLeft <= right) { arr[tempLeft] = temp[t]; t += 1 ; tempLeft += 1 ; } } }
在经过时间测试我们发现其速度很快。
7.3.7 基数排序
基数排序属于一种分配式排序,又称为桶子法,bin sort ,顾名思意,是通过键值的各个位的值,将要排序的元素分配到某些桶中,达到排序的作用。
基数排序是一种稳定的排序,基数排序法的是效率高的稳定性排序法
基数排序是桶排序的扩展
基数排序起源于1887年赫尔曼.何乐礼发明的,将整数按照位数切割成不同的数字,然后按每个位数分别比较。
所有待比较数值统一为同样的数位长度,数位较短的数前面补零 。然后,从最低位开始 ,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后 , 数列就变成一个有序序列
思路:
使用基数排序,进行升序排序
将每个元素的各位数取出,然后看这个数应该放在哪个对应的桶
按照这个桶的顺序(一维数组的下标取出数据,放入原来数组)
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 public class RadixSort { public static void main (String[] args) { int [] arr={53 , 3 , 542 , 748 , 14 , 214 }; bucketSort(arr); } public static void bucketSort (int [] arr) { int [] bucketRecord=new int [10 ]; int [][] bucket=new int [arr.length][10 ]; for (int i = 0 ; i < arr.length; i++) { int sub=arr[i]/1 %10 ; bucket[bucketRecord[sub]][sub]=arr[i]; bucketRecord[sub]= bucketRecord[sub]+1 ; } int index=0 ; for (int i = 0 ; i < bucketRecord.length; i++) { if (bucketRecord[i]>0 ){ for (int j = 0 ; j < bucketRecord[i]; j++) { arr[index++]=bucket[j][i]; bucket[j][i]=0 ; } bucketRecord[i]=0 ; } } index=0 ; System.out.println("第1次排序后得到的结果:" + Arrays.toString(arr)); for (int i = 0 ; i < arr.length; i++) { int sub=arr[i]/10 %10 ; bucket[bucketRecord[sub]][sub]=arr[i]; bucketRecord[sub]= bucketRecord[sub]+1 ; } for (int i = 0 ; i < bucketRecord.length; i++) { if (bucketRecord[i]>0 ){ for (int j = 0 ; j < bucketRecord[i]; j++) { arr[index++]=bucket[j][i]; bucket[j][i]=0 ; } bucketRecord[i]=0 ; } } index=0 ; System.out.println("第2次排序后得到的结果:" + Arrays.toString(arr)); for (int i = 0 ; i < arr.length; i++) { int sub=arr[i]/100 %10 ; bucket[bucketRecord[sub]][sub]=arr[i]; bucketRecord[sub]= bucketRecord[sub]+1 ; } for (int i = 0 ; i < bucketRecord.length; i++) { if (bucketRecord[i]>0 ){ for (int j = 0 ; j < bucketRecord[i]; j++) { arr[index++]=bucket[j][i]; bucket[j][i]=0 ; } bucketRecord[i]=0 ; } } index=0 ; System.out.println("第3次排序后得到的结果:" + Arrays.toString(arr)); } }
使用for循环:
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 public class RadixSort2 { public static void main (String[] args) { int [] arr={53 , 3 , 542 , 78 , 14 , 214 ,12 ,24 ,56 ,66 ,70 }; bucket(arr); } public static int maxLength (int [] arr) { int max=arr[0 ]; for (int i = 0 ; i < arr.length; i++) { if (max<arr[i]){ max=arr[i]; } } int length=(max+"" ).length(); return length; } public static void bucket (int [] arr) { int [][] bucket=new int [arr.length][10 ]; int [] bucketRecord=new int [10 ]; int length = maxLength(arr); for (int z = 0 , n=1 ; z < length; n=n*10 ,z++) { for (int i = 0 ; i < arr.length; i++) { int sub=arr[i]/n%10 ; bucket[bucketRecord[sub]][sub]=arr[i]; bucketRecord[sub]= bucketRecord[sub]+1 ; } int index=0 ; for (int i = 0 ; i < bucketRecord.length; i++) { if (bucketRecord[i]>0 ){ for (int j = 0 ; j < bucketRecord[i]; j++) { arr[index++]=bucket[j][i]; bucket[j][i]=0 ; } bucketRecord[i]=0 ; } } System.out.println("第" +z+"次排序后得到的结果:" +Arrays.toString(arr)); } } }
基数排序是对传统桶排序的扩展,速度很快 .
基数排序是经典的空间换时间的方式,占用内存很大 , 当对海量数据排序时,容易造OutOfMemoryError 基数排序时稳定的 。
[注:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的] 有负数的数组
7.3.8 堆排序 8. 查找 8. 1 线性查找 很简单的查找方式,通过逐一比对,得到目标值,然后呢返回下标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class SeqSearch { public static void main (String [] args) { int [] arr = {7 ,3 ,4 ,11 ,434 ,55 ,0 ,23 }; int index = seqSearch(arr,55 ); if (index == -1 ){ System.out.println("没有查找到" ); }else { System.out.println("找到了,下标为:" + index); System.out.println("此时输出的结果为:" + arr[index]); } } public static int seqSearch (int [] arr ,int value) { for (int i = 0 ; i < arr.length ; i ++){ if (value == arr[i]){ return i; } } return -1 ; } }
8.2 二分查找
确定数中间的下标 mid = (right + left) / 2;
然后让需要查找的数findValue和arr[mid] 比较
findVal > arr[mid],说明你要查找的数,在mid的右边,因此需要递归的向右查找
如果findVal < arr[mid],说明你要查找的数在mid的左边,因此需要递归的向左边查找
findVal == arr[mid] 说明找到,就返回
什么时候我们需要结束递归
找到就结束递归
递归完整整个数组,仍然没有找到findVal,也需要结束递归,当left > right就需要退出
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 public class BinarySearch { public static void main (String[] args) { int [] arr = {1 ,4 ,6 ,7 ,9 ,12 ,24 ,56 ,78 }; int resIndex = binarySearch(arr, 0 , arr.length -1 , 78 ); if (resIndex == -1 ) { System.out.println("此时没找到" ); }else { System.out.println("找到了,其下标为:" + resIndex); } } public static int binarySearch (int [] arr ,int left,int right,int findVal) { if (left > right) { return -1 ; } int mid = (left + right) / 2 ; int midVal = arr[mid]; if (findVal > midVal) { return binarySearch(arr, mid +1 , right, findVal); }else if (findVal < midVal){ return binarySearch(arr, left, mid - 1 , findVal); }else { return mid; } } }
例题:
当一个有序数组中,有多个相同的数据时,如果将所有的数值都查找到,比如这里的1000
arr={12,23,34,44,45,46,46,46,47,48};
在找到mid索引值,不要马上返回
向mid索引值的左边扫描,将所有满足46的元素的下标,加入到arrayList
向mid索引值的右边扫描,将所有满足46的元素的下标,加入到arrayList
将ArrayList返回
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 BinarySearch { public static void main (String[] args) { int [] arr = {1 ,4 ,6 ,7 ,9 ,12 ,12 ,24 ,56 ,78 }; BinarySearch2(arr, 0 , arr.length -1 , 12 ); } public static int binarySearch (int [] arr ,int left,int right,int findVal) { if (left > right) { return -1 ; } int mid = (left + right) / 2 ; int midVal = arr[mid]; if (findVal > midVal) { return binarySearch(arr, mid +1 , right, findVal); }else if (findVal < midVal){ return binarySearch(arr, left, mid - 1 , findVal); }else { return mid; } } public static void BinarySearch2 (int [] array,int left,int right, int value) { int mid = (left + right)/2 ; ArrayList<Integer> resIndexlist = new ArrayList <Integer>(); if (value < array[mid]) { BinarySearch2(array,left,mid-1 ,value); }else if (value > array[mid]) { BinarySearch2(array,mid+1 ,right,value); }else { System.out.println(mid); int temp = mid -1 ; while (true ) { if (temp < 0 ||array[temp]!= value) { break ; } resIndexlist.add(temp); temp--; } resIndexlist.add(mid); temp = mid + 1 ; while (true ) { if (array[temp]!= value || temp > array.length-1 ) { break ; } resIndexlist.add(temp); temp++; } System.out.println(resIndexlist.toString()); } } }
8.3 差值查找 差值查找算法是在二分查找算法的基础上进行进一步进行的,其是在二分查找的基础上做了一点小小的优化,其基本思路是将原先公式1的方式变成公式2的方式,由于其取中点值的方式不同,造成其效率也不同,
由于二分查找需要的条件将数组进行有序排列,故而再在此low代表左边,hight代表右边。
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 public class InterpolationSearch { public static void main (String[] args) { System.out.println("======== 差值查找分布略微不均匀数组 ===========" ); int arr[] = {1 ,4 , 9 , 15 , 27 , 49 , 128 , 157 }; int result = interpolationSearch(arr, 9 , 0 , arr.length - 1 ); System.out.println(result==-1 ?"未找到元素!" :"查找成功!" + 9 + "对应下标: " + result); System.out.println("======== 差值查找分布均匀数组 ===========" ); int arr1[] = {0 , 2 , 4 , 6 , 8 , 10 , 12 , 14 , 16 , 18 ,20 }; int target = 8 ; int result1 = interpolationSearch(arr1, target, 0 , arr1.length - 1 ); System.out.println(result1==-1 ?"未找到元素!" :"查找成功!" + target + "对应下标: " + result1); System.out.println("======== 差值查找分布极其不均匀数组 ===========" ); int arr2[] = {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ,99999 }; int target2 = 8 ; int result2 = interpolationSearch(arr2, target2, 0 , arr2.length - 1 ); System.out.println(result2==-1 ?"未找到元素!" :"查找成功!" + target2 + "对应下标: " + result2); } public static int interpolationSearch (int []arr, int target ,int left , int right) { if (target < arr[left] || target > arr[right] || left > right) { return -1 ; } int leftToTarget = target - arr[left]; int rightToLeft = arr[right] - arr[left]; int num = right - left; int mid = left + (leftToTarget * num) /rightToLeft; System.out.println("找了一次,中间值为:" + mid); if (arr[mid] > target && left <= right) { return interpolationSearch(arr, target, left, mid - 1 ); }else if (arr[mid] < target && left <= right) { return interpolationSearch(arr, target, mid + 1 , right); }else { return mid; } } }
8.4 斐波那契查找 斐波那契查找算法,又被称为黄金分割法,是一种有序的查找算法。斐波那契数列被称为黄金分割数列,其数列如: 1,1,2,3,5,8,13,31…….。在数学上,其递归方法被定义为
该数列越往后的两个数的比值趋向于黄金比例0.618
斐波那契查找是在二分查找的基础上根据斐波那契数列进行分割 。在斐波那契数列找一个等于大于查找表中元素个数的数F(n),将原查找表扩展为长度为F(n)(如果要补充元素,则补充重复最后一个元素,直到满足F(n)个元素),完成后进行斐波那契分割,即F[n]个元素分割为前半部分F(n-1)个元素,后半部分F(n-2)个元素,找出要查找的元素在那一部分并递归 ,直到找到。斐波那契查找的时间复杂度是O(log2n) 。
1)由斐波那契数列 F[k]=F[k-1]+F[k-2] 的性质,可以得到 (F[k]-1)=(F[k-1]-1)+(F[k-2]-1)+1 。该式说明:只要顺序表的长度为F[k]-1,则可以将该表分成长度为F[k-1]-1和F[k-2]-1的两段,即如上图所示。从而中间位置为mid=low+F(k-1)-1;
2)每一子段也可以用相同的方式分割;
3)若顺序表长度n不一定刚好等于F[k]-1,则需要将原来的顺序表长度n增加至F[k]-1。这里的k值只要能使得F[k]-1恰好大于或等于n即可。
例如:原数组为{1,5,6,33,55,233,455},长度n为7,若 F[ k ] - 1等于9,则应该将原数组扩容到9,多出的位置使用high位置的455填充,即得到{1,5,6,33,55,233,455,455,455}。
代码实现
(1)我们需要先递归实现斐波拉契数列,然后根据原数组的大小计算斐波拉契数列的k值;
(2)数组扩容条件是:增大 k 值(索引从 0 开始),使得数组长度刚好大于或者等于斐波那契数列中的 F[k]-1 ,我们定义临时数组 temp ,temp 后面为 0 的元素都按照数组最大元素值填充 ;
(3)mid值的确定:mid = low + f[k - 1] - 1 ,即用黄金分割点确定 mid 的值;
(4)value<temp[mid] :目标值在黄金分割点的左边,因为全部元素 = 前面的元素 + 后边元素即 f[k] = f[k-1] + f[k-2],又前面有 f[k-1]个元素,所以可以继续拆分 f[k-1] = f[k-2] + f[k-3],即下次循环有mid=low+f[k-1-1]+1,即需要k-=1 ;
value> temp[mid] :目标值在黄金分割点的右边。因为全部元素 = 前面的元素 + 后边元素即 f[k] = f[k-1] + f[k-2],又前面有 f[k-2]个元素,所以可以继续拆分 f[k-1] = f[k-3] + f[k-4],即下次循环有mid=low+f[k-1-2]+1,即需要k-=2 ;
value== temp[mid] : 找到目标值,因为数组经历过扩容,后面的值其实有些是多余的,mid 可能会越界(相对于原数组来说)则有: 1)mid <= high :证明 mid 索引在原数组中,返回 mid ; 2)mid > high 时,证明 mid 索引已经越界(相对于原数组来说),返回 high ;
(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 public class FibonacciSearch { public static void main (String[] args) { int arr[] = {1 ,5 ,6 ,33 ,55 ,233 ,455 }; Arrays.sort(arr); int value = 55 ; int temp = fibonacciSearch(arr, value); System.out.println(temp == -1 ? "没找到你所要的值,可能你要查找的值并不在斐波那契数列之中,你要查找的值为:" +value :"您查找的值为" +value+"其下标为" +temp ); } public static final int maxSize = 20 ; public static int [] fibonacci() { int [] fib = new int [maxSize]; fib[0 ] = 1 ; fib[1 ] = 1 ; for (int i = 2 ; i < maxSize ; i++ ) { fib[i] = fib[i - 1 ] + fib [i -2 ]; } return fib; } public static int fibonacciSearch (int [] arr ,int value ) { int low = 0 ; int high = arr.length - 1 ; int k = 0 ; int mid = 0 ; int fib[] = fibonacci(); while (high > fib[k] - 1 ) { k ++; } int [] temp = Arrays.copyOf(arr, fib[k]); for (int i = high + 1 ; i < temp.length ; i++ ) { temp[i] = arr[high]; } while (low < high) { mid = low + fib[k - 1 ] -1 ; if (value < temp[mid]) { high = mid - 1 ; k -= 1 ; }else if (value > temp[mid]) { low = mid + 1 ; k -= 2 ; }else { if (mid <= high) { return mid; }else { return high; } } } return - 1 ; } }