数据结构与算法(二)——线性表
一、线性表
1、介绍
由零个或多个数据元素组成的有限序列,有序表。
数据类型:一组性质相同的值的集合及定义在此集合上的一些操作的总称。
2、顺序存储结构
顺序表:用一组地址连续的存储单元存放数据。
特点:具有随机存储结构的特点,时间复杂度为o(1)。存、读数据时,不管哪个位置,时间复杂度都是o(1);插入和删除的时间复杂度是o(n)。
优点:无需为表示元素之间的逻辑关系而增加额外的存储空间;可以快速的存取表中任意位置的元素。
缺点:插入和删除操作需要移动大量元素;当线性表长度变化较大时,难以确定存储空间的容量;容易造成存储空间的"碎片"。
常见结构:数组、栈。
3、链式存储结构
链表:由一个数据域和一个指向下一个元素地址的指针域构成。存放数据的地址不一定连续。链表又主要包括:单(向)链表,单(向)循环链表,双(向)链表,双(向)循环链表。
常见结构:链表、队列。
4、顺序表与链表区别
结论:若线性表需要频繁查找,很少进行插入和删除操作,采用顺序表;若需要频繁插入和删除,采用链表。
二、稀疏数组
1、介绍
2、二维数组、稀疏数组相互转化
代码示例:
1 public class SparseArray { 2 // 构建稀疏数组.二维数组 --> 稀疏数组 3 public int[][] createSparseArr(int[][] chess) { 4 final int sum = getCount(chess); 5 6 // 创建的稀疏数组 7 int[][] sparseArr = new int[sum + 1][3]; 8 sparseArr[0][0] = chess.length; 9 sparseArr[0][1] = chess[0].length; 10 sparseArr[0][2] = sum; 11 12 // count 用于记录是第几个非 0 数据 13 int count = 0; 14 for (int i = 0; i < chess.length; i++) { 15 for (int j = 0; j < chess[i].length; j++) { 16 if (chess[i][j] != 0) { 17 count++; 18 sparseArr[count][0] = i; 19 sparseArr[count][1] = j; 20 sparseArr[count][2] = chess[i][j]; 21 } 22 } 23 } 24 25 return sparseArr; 26 } 27 28 // 恢复稀疏数组.稀疏数组 --> 二维数组 29 public int[][] recover(int[][] sparseArr) { 30 int[][] chessArr = new int[sparseArr[0][0]][sparseArr[0][1]]; 31 for (int i = 1; i < sparseArr.length; i++) { 32 chessArr[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2]; 33 } 34 35 return chessArr; 36 } 37 38 // 遍历二维数组.得到非 0 数据的个数 39 private int getCount(int[][] chess) { 40 int sum = 0; 41 for (int[] ints : chess) { 42 for (int anInt : ints) { 43 if (anInt != 0) { 44 sum++; 45 } 46 } 47 } 48 49 return sum; 50 } 51 52 public void print(int[][] chess) { 53 for (int[] row : chess) { 54 for (int data : row) { 55 System.out.printf("%d\t", data); 56 } 57 System.out.println(); 58 } 59 System.out.println(); 60 } 61 62 }
代码示例:测试类
1 public class Main { 2 public static void main(String[] args) { 3 SparseArray sparseArray = new SparseArray(); 4 // 0-没有棋子.1-黑子,2-白子 5 int[][] chess = new int[4][5]; 6 chess[1][2] = 6; 7 chess[2][3] = 9; 8 System.out.println("原始的二维数组~~"); 9 sparseArray.print(chess); 10 11 // 1.创建稀疏数组 12 final int[][] sparseArr = sparseArray.createSparseArr(chess); 13 System.out.println("得到稀疏数组为~~~~"); 14 for (int i = 0; i < sparseArr.length; i++) { 15 System.out.printf("%d\t%d\t%d\t\n", sparseArr[i][0], sparseArr[i][1], sparseArr[i][2]); 16 } 17 18 // 2.恢复稀疏数组 19 final int[][] chessArr = sparseArray.recover(sparseArr); 20 21 System.out.println("\n恢复后的二维数组"); 22 sparseArray.print(chessArr); 23 } 24 25 }
三、单(向)链表
1、介绍
单链表:每个结点只包含一个指针域。头指针是链表的必要元素。头结点不是。
带有头结点的单链表:
不带有头结点的单链表:
代码示例:单链表结构
1 class Node<E> { 2 public E item; 3 public Node next; 4 5 public Node(E item) { 6 this.item = item; 7 } 8 }
2、基本操作
插入:①、s->next = p->next,②、p->next = s
删除:①、p->next = p->next->next
3、英雄排行榜问题
代码示例:带头结点的单链表,英雄排行榜实现
1 // 带有头结点的单链表 2 public class SingleLinkedList { 3 4 // 头结点 5 private final Node head; 6 7 public SingleLinkedList() { 8 // 初始化头结点 9 head = new Node(0, null); 10 } 11 12 public Node getHead() { 13 return head; 14 } 15 16 /** 17 * 添加结点到单向链表.直接添加到末尾.rank可重复 18 * 19 * @param node 20 */ 21 public void add(Node node) { 22 Node temp = head; 23 24 while (true) { 25 if (temp.next == null) { 26 temp.next = node; 27 break; 28 } else { 29 temp = temp.next; 30 } 31 } 32 } 33 34 /** 35 * 添加结点到单向链表.按结点的 rank 属性排名.rank不可重复 36 * 37 * @param node 38 */ 39 public void addByOrder(Node node) { 40 Node temp = head; 41 42 while (true) { 43 if (temp.next == null) { 44 temp.next = node; 45 break; 46 } 47 48 if (node.rank == temp.next.rank) { 49 System.out.println("此排名已存在!添加失败! no = " + node.rank); 50 break; 51 } else if (node.rank < temp.next.rank) { 52 /* 插入 */ 53 node.next = temp.next; 54 temp.next = node; 55 break; 56 } else { 57 temp = temp.next; 58 } 59 } 60 61 } 62 63 /** 64 * 修改指定的结点 65 * 66 * @param node 67 */ 68 public void update(Node node) { 69 Node temp = head; 70 71 while (true) { 72 if (temp.next == null) { 73 System.out.println("此排名不存在!修改失败 no = " + node.rank); 74 break; 75 } 76 77 if (temp.next.rank == node.rank) { 78 /* 修改 */ 79 temp.next.name = node.name; 80 break; 81 } else { 82 temp = temp.next; 83 } 84 } 85 } 86 87 /** 88 * 删除指定的结点 89 * 90 * @param node 91 */ 92 public void delete(Node node) { 93 Node temp = head; 94 95 while (true) { 96 if (temp.next == null) { 97 System.out.println("此排名不存在!删除失败 no = " + node.rank); 98 break; 99 } 100 101 if (temp.next.rank == node.rank) { 102 /* 删除 */ 103 temp.next = temp.next.next; 104 break; 105 } else { 106 temp = temp.next; 107 } 108 } 109 } 110 111 /** 112 * 单链表的结点个数 113 */ 114 public int size() { 115 Node temp = head; 116 117 int size = 0; 118 while (temp.next != null) { 119 size++; 120 temp = temp.next; 121 } 122 123 return size; 124 } 125 126 /** 127 * 打印链表 128 */ 129 public void list() { 130 Node temp = head.next; 131 132 while (true) { 133 if (temp == null) { 134 break; 135 } else { 136 System.out.println(temp); 137 temp = temp.next; 138 } 139 } 140 } 141 142 } 143 144 // 单链表的结点 145 class Node { 146 /** 147 * rank 是 >= 1 的整数 148 */ 149 public int rank; 150 public String name; 151 public Node next; 152 153 public Node(int rank, String name) { 154 this.rank = rank; 155 this.name = name; 156 } 157 158 @Override 159 public String toString() { 160 return "Node{" + 161 "rank=" + rank + 162 ", name='" + name + '\'' + 163 '}'; 164 } 165 }
代码示例:测试类
1 // 测试类 2 public class Main { 3 public static void main(String[] args) { 4 Node node1 = new Node(1, "宋江"); 5 Node node2 = new Node(2, "卢俊义"); 6 Node node3 = new Node(3, "吴用"); 7 Node node4 = new Node(4, "林冲"); 8 9 // 头指针 10 SingleLinkedList singleLinkedList = new SingleLinkedList(); 11 12 // 1.直接添加到链表尾部 13 // singleLinkedList.add(node1); 14 // singleLinkedList.add(node2); 15 // singleLinkedList.add(node3); 16 // singleLinkedList.add(node4); 17 // 18 // System.out.println("按顺序直接添加到链表尾部~~"); 19 // singleLinkedList.list(); 20 21 // 2.按排序添加到链表 22 singleLinkedList.addByOrder(node1); 23 singleLinkedList.addByOrder(node4); 24 singleLinkedList.addByOrder(node3); 25 singleLinkedList.addByOrder(node2); 26 27 System.out.println("按排序添加到链表~~"); 28 singleLinkedList.list(); 29 30 // 3.更新 31 Node newNode = new Node(4, "小林"); 32 System.out.println("更新后链表~~" + "链表长度~" + singleLinkedList.size()); 33 singleLinkedList.update(newNode); 34 singleLinkedList.list(); 35 36 // 4.删除 37 singleLinkedList.delete(newNode); 38 System.out.println("删除后链表~~" + "链表长度~" + singleLinkedList.size()); 39 singleLinkedList.list(); 40 } 41 }
四、单(向)循环链表
1、介绍
带有头结点的单循环链表:
单链表,单循环链表的区别在于空链表的判断。单链表:head->next == null,单循环链表:head->next == head。
2、约瑟夫问题
问题:n 个结点,从 k(1 <= k <= n)开始报数,数到 m ,求出列序列。
示例:当 n = 5,k = 1,m = 2 时,出列序列:2,4,1,5,3
代码示例:不带头结点的单循环链表,约瑟夫问题实现
1 // 不带头结点的单循环链表 2 public class SingleCircleLinkedList { 3 4 // 头指针 5 private Node head; 6 7 // 单循环链表的大小 8 private int n; 9 10 public SingleCircleLinkedList(int size) { 11 if (size < 1) { 12 System.out.println("初始化单循环链表大小size值不正确"); 13 return; 14 } 15 this.n = size; 16 17 // 1.创建第一个结点. 18 head = new Node(1); 19 head.next = head; 20 21 Node tail = head; 22 if (size == 1) { 23 return; 24 } 25 26 // 2.构建循环链表 27 for (int i = 2; i <= size; i++) { 28 final Node node = new Node(i); 29 30 tail.next = node; 31 node.next = head; 32 33 tail = node; 34 } 35 } 36 37 /** 38 * 约瑟夫环出列 39 * 40 * @param k 第 k 个结点开始报数 41 * @param m 数到 m 42 */ 43 public void listing(int k, int m) { 44 // 参数校验 45 if (head == null || k < 1 || k > n) { 46 System.out.println("空链表或参数输入有误,请重新输入!"); 47 return; 48 } 49 50 // 尾指针.(其实这个在构造器中可以拿到). 51 // 作用:在结点出列的时候用于单链表的删除. 52 Node tail = head; 53 while (tail.next != head) { 54 tail = tail.next; 55 } 56 57 // 1.第 k 个结点开始报数.前进 k - 1 次 58 for (int i = 1; i < k; i++) { 59 head = head.next; 60 tail = tail.next; 61 } 62 63 // 2.开始出列 64 while (head != tail) { 65 // 3.数到 m.前进 m - 1 次 66 for (int i = 1; i < m; i++) { 67 head = head.next; 68 tail = tail.next; 69 } 70 71 // 4.head指向的结点就是出列的结点 72 System.out.println(head.no + " 出列"); 73 74 // 5.修改指针. 75 head = head.next; 76 tail.next = head; 77 } 78 79 // 6.剩最后一个结点 80 System.out.println("最后一个结点 " + head.no + " 出列"); 81 } 82 83 public void show() { 84 if (head == null) { 85 System.out.println("链表为空~"); 86 return; 87 } 88 89 // 头指针不动 90 Node temp = head; 91 92 while (true) { 93 System.out.println(temp.no); 94 95 if (temp.next == head) { 96 break; 97 } 98 99 temp = temp.next; 100 } 101 } 102 103 private static class Node { 104 /** 105 * 编号,no >= 1 的整数 106 */ 107 public int no; 108 109 public Node next; 110 111 public Node(int no) { 112 this.no = no; 113 } 114 115 } 116 117 }
代码示例:测试类
1 // 测试类 2 public static void main(String[] args) { 3 4 SingleCircleLinkedList singleCircleLinkedList = new SingleCircleLinkedList(5); 5 // singleCircleLinkedList.show(); 6 7 // 出列 8 singleCircleLinkedList.listing(1, 2); 9 } 10 11 // 结果 12 2 出列 13 4 出列 14 1 出列 15 5 出列 16 最后一个结点 3 出列
五、双(向)链表
1、介绍
双链表:每个结点包含两个指针域。
带有头结点的双链表:
代码示例:双链表结构
1 class Node<E> { 2 public E item; 3 public Node<E> next; 4 public Node<E> prev; 5 6 public Node(E item) { 7 this.item = item; 8 } 9 }
2、基本操作
插入:顺序很重要,不能写反了!
①、s->next = p->next
②、s->prev = p
③、p->next->prev = s
④、p->next = s
删除:①、p->prev->next = p->next,②、p->next->prev = p->prev
3、英雄排行榜问题
代码示例:带头结点的双链表,英雄排行榜实现
1 // 带有头结点的双链表 2 public class DoubleLinkedList { 3 4 // 头结点 5 private final DoubleNode head; 6 7 public DoubleLinkedList() { 8 // 初始化头结点 9 head = new DoubleNode(0, null); 10 } 11 12 public DoubleNode getHead() { 13 return head; 14 } 15 16 /** 17 * 添加结点到双向链表.直接添加到末尾.rank可重复 18 * 19 * @param node 20 */ 21 public void add(DoubleNode node) { 22 DoubleNode temp = head; 23 24 while (true) { 25 if (temp.next == null) { 26 temp.next = node; 27 node.prev = temp; 28 break; 29 } else { 30 temp = temp.next; 31 } 32 } 33 } 34 35 /** 36 * 添加结点到双向链表.按结点的 rank 属性排名.rank不可重复 37 * 38 * @param node 39 */ 40 public void addByOrder(DoubleNode node) { 41 DoubleNode temp = head; 42 43 while (true) { 44 if (temp.next == null) { 45 temp.next = node; 46 node.prev = temp; 47 break; 48 } 49 50 if (node.rank == temp.next.rank) { 51 System.out.println("此排名已存在!添加失败! no = " + node.rank); 52 break; 53 } else if (node.rank < temp.next.rank) { 54 /* 插入 */ 55 node.next = temp.next; 56 node.prev = temp; 57 temp.next.prev = node; 58 temp.next = node; 59 break; 60 } else { 61 temp = temp.next; 62 } 63 } 64 65 } 66 67 /** 68 * 修改指定的结点 69 * 70 * @param node 71 */ 72 public void update(DoubleNode node) { 73 DoubleNode temp = head; 74 75 while (true) { 76 if (temp.next == null) { 77 System.out.println("此排名不存在!修改失败 no = " + node.rank); 78 break; 79 } 80 81 if (temp.next.rank == node.rank) { 82 /* 修改 */ 83 temp.next.name = node.name; 84 break; 85 } else { 86 temp = temp.next; 87 } 88 } 89 } 90 91 /** 92 * 删除指定的结点 93 * 94 * @param node 95 */ 96 public void delete(DoubleNode node) { 97 DoubleNode temp = head.next; 98 99 while (true) { 100 if (temp == null) { 101 System.out.println("此排名不存在!删除失败 no = " + node.rank); 102 break; 103 } 104 105 if (temp.rank == node.rank) { 106 /* 删除 */ 107 temp.prev.next = temp.next; 108 if (temp.next != null) { 109 temp.next.prev = temp.prev; 110 } 111 break; 112 } else { 113 temp = temp.next; 114 } 115 } 116 } 117 118 /** 119 * 打印链表 120 */ 121 public void list() { 122 DoubleNode temp = head.next; 123 124 while (true) { 125 if (temp == null) { 126 break; 127 } else { 128 System.out.println(temp); 129 temp = temp.next; 130 } 131 } 132 } 133 } 134 135 /** 136 * 双链表的结点 137 */ 138 class DoubleNode { 139 /** 140 * rank 是 >= 1 的整数 141 */ 142 public int rank; 143 public String name; 144 public DoubleNode next; 145 public DoubleNode prev; 146 147 public DoubleNode(int rank, String name) { 148 this.rank = rank; 149 this.name = name; 150 } 151 152 @Override 153 public String toString() { 154 return "DoubleNode{" + 155 "rank=" + rank + 156 ", name='" + name + '\'' + 157 '}'; 158 } 159 }
代码示例:测试类
1 // 测试类 2 public static void main(String[] args) { 3 DoubleNode node1 = new DoubleNode(1, "宋江"); 4 DoubleNode node2 = new DoubleNode(2, "卢俊义"); 5 DoubleNode node3 = new DoubleNode(3, "吴用"); 6 DoubleNode node4 = new DoubleNode(4, "林冲"); 7 8 // 头指针 9 DoubleLinkedList doubleLinkedList = new DoubleLinkedList(); 10 11 // 1.直接添加到链表尾部 12 // doubleLinkedList.add(node1); 13 // doubleLinkedList.add(node2); 14 // doubleLinkedList.add(node3); 15 // doubleLinkedList.add(node4); 16 17 // System.out.println("按顺序直接添加到链表尾部~~"); 18 // doubleLinkedList.list(); 19 20 // 2.按排序添加到链表 21 doubleLinkedList.addByOrder(node1); 22 doubleLinkedList.addByOrder(node4); 23 doubleLinkedList.addByOrder(node3); 24 doubleLinkedList.addByOrder(node2); 25 26 System.out.println("按排序添加到链表~~"); 27 doubleLinkedList.list(); 28 29 // 3.更新 30 DoubleNode newNode = new DoubleNode(4, "小林"); 31 doubleLinkedList.update(newNode); 32 System.out.println("更新后链表~~"); 33 doubleLinkedList.list(); 34 35 // 4.删除 36 doubleLinkedList.delete(newNode); 37 System.out.println("删除后链表~~"); 38 doubleLinkedList.list(); 39 }
六、双(向)循环链表
1、介绍
带有头结点的双循环链表:
2、问题
要求用双向循环链表实现,用户输入一个数使得26个字母的排列发生变化,例如:输入3,输出结果:DEFGHIJKLMNOPQRSTUVWXYZABC
同时需要支持负数,例如:输入-3,输出结果:XYZABCDEFGHIJKLMNOPQRSTUVW
请读者自行实现。
作者:Craftsman-L
本博客所有文章仅用于学习、研究和交流目的,版权归作者所有,欢迎非商业性质转载。
如果本篇博客给您带来帮助,请作者喝杯咖啡吧!点击下面打赏,您的支持是我最大的动力!