1.线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表
1.概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储。
- 动态顺序表:使用动态开辟的数组存储。
静态顺序表适用于确定知道需要存多少数据的场景.
静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用.
相比之下动态顺序表更灵活, 根据需要动态的分配空间大小.
2.接口实现
我们来实现一个动态顺序表. 以下是需要支持的接口.
public class SeqList {
// 打印顺序表
public void display() { }
// 在 pos 位置新增元素
public void add(int pos, int data) { }
// 判定是否包含某个元素
public boolean contains(int toFind) { return true; }
// 查找某个元素对应的位置
public int search(int toFind) { return -1; }
// 获取 pos 位置的元素
public int getPos(int pos) { return -1; }
// 给 pos 位置的元素设为 value
public void setPos(int pos, int value) { }
//删除第一次出现的关键字key
public void remove(int toRemove) { }
// 获取顺序表长度
public int size() { return 0; }
// 清空顺序表
public void clear() { }
}
3.顺序表功能实现
public class TestDemo {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
}
}
public class MyArrayList {
public int[] elem;
public int useSize;//当前有效的数据个数
public MyArrayList() {
this.elem = new int[10];
}
1.打印顺序表
public void display() {
for (int i = 0; i < this.useSize; i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
public class TestDemo {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(0,1);
myArrayList.add(1,2);
myArrayList.add(2,3);
myArrayList.add(3,4);
myArrayList.display();
}
}
2.获取顺序表的有效数据长度
public int size() {
return this.useSize;
}
public class TestDemo {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(0,1);
myArrayList.add(1,2);
myArrayList.add(2,3);
myArrayList.add(3,4);
myArrayList.display();
System.out.println(myArrayList.size());
}
}
3. 在 pos 位置新增元素
实现过程:
1.判断位置是否合法。
2.判断数据满没满,没满进行扩容。
3.数组往后移,空出插入的位置。
4.在空出的位置进行插入。
具体代码如下:
public void add(int pos, int data) {
if(pos < 0 || pos > useSize) {
System.out.println("pos位置不合法");
return;
}
if(isFull()) {
this.elem = Arrays.copyOf(this.elem,2 * this.elem.length);
}
for (int i = this.useSize - 1; i >= pos; i--) {
this.elem[i + 1] =this.elem[i];
}
this.elem[pos] = data;
this.useSize++;
}
//判断数组是否已满
public boolean isFull() {
return useSize == elem.length;
}
public class TestDemo {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(0,1);
myArrayList.add(1,2);
myArrayList.add(2,3);
myArrayList.add(3,4);
myArrayList.display();
}
}
4.判断是否包含某元素
public boolean contains(int toFind) {
for (int i = 0; i < this.useSize; i++) {
if(this.elem[i] == toFind) {
return true;
}
}
return false;
}
public class TestDemo {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(0,1);
myArrayList.add(1,2);
myArrayList.add(2,3);
myArrayList.add(3,4);
myArrayList.display();
System.out.println(myArrayList.contains(3));
}
}
5.查找某个元素对应的位置
public int search(int toFind) {
for (int i = 0; i < this.useSize; i++) {
if(this.elem[i] == toFind) {
return i;
}
}
return -1;
}
public class TestDemo {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(0,1);
myArrayList.add(1,2);
myArrayList.add(2,3);
myArrayList.add(3,4);
myArrayList.display();
System.out.println(myArrayList.search(2));
}
}
6.获取 pos 位置的元素
public int getPos(int pos) {
if(pos < 0 || pos >= this.useSize) {
System.out.println("pos位置不合法");
return -1;//所以这里说下,业务上的处理这里不考虑,后期可以抛异常
}
if(isEmpty()) {
System.out.println("顺序表为空!");
return -1;
}
return this.elem[pos];
}
//判断是否为空
public boolean isEmpty() {
return this.useSize == 0;
}
public class TestDemo {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(0,1);
myArrayList.add(1,2);
myArrayList.add(2,3);
myArrayList.add(3,4);
myArrayList.display();
System.out.println(myArrayList.getPos(3));
}
}
7. 给 pos 位置的元素设为/更新 value
public void setPos(int pos, int value) {
if(pos < 0 || pos >= this.useSize) {
System.out.println("pos位置不合法");
return;
}
if(isEmpty()) {
System.out.println("顺序表为空!");
return;
}
this.elem[pos] = value;
}
public class TestDemo {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(0,1);
myArrayList.add(1,2);
myArrayList.add(2,3);
myArrayList.add(3,4);
myArrayList.display();
myArrayList.setPos(0,99);
myArrayList.display();
}
}
8.删除第一次出现的关键字key
public void remove(int toRemove) {
if(isEmpty()) {
System.out.println("顺序表为空!");
return;
}
int index = search(toRemove);
if(index == -1) {
System.out.println("没有你要删除的数字");
return;
}
for (int i = index; i < this.useSize - 1; i++) {
this.elem[i] = this.elem[i+1];
}
this.useSize--;
//this.elem[useSize] == null;如果数组当中是引用数据类型
}
public class TestDemo {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(0,1);
myArrayList.add(1,2);
myArrayList.add(2,3);
myArrayList.add(3,4);
myArrayList.display();
myArrayList.remove(3);
myArrayList.display();
}
}
9.清空顺序表
public void clear() {
this.useSize = 0;
//如果数组当中是引用数据类型
/* for (int i = 0; i < useSize; i++) {
this.elem[i] = null;
}
this.useSize = 0;
*/
}
public class TestDemo {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(0,1);
myArrayList.add(1,2);
myArrayList.add(2,3);
myArrayList.add(3,4);
myArrayList.display();
myArrayList.clear();
myArrayList.display();
}
}
4.顺序表的问题
- 顺序表中间/头部的插入删除,时间复杂度为O(N)。
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
如何解决以上问题呢?下面给出了链表的结构来看看。
3.链表
1.链表的概念及结构
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
- 单向、双向
- 带头、不带头
- 循环、非循环
虽然有这么多的链表的结构,但是我们重点掌握两种:
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。
2.无头单向非循环链表
public class MyLinkedList {
public ListNode head;//链表的头引用
public void creatList() {
ListNode listNode1 = new ListNode(12);
ListNode listNode2 = new ListNode(23);
ListNode listNode3 = new ListNode(34);
ListNode listNode4 = new ListNode(45);
ListNode listNode5 = new ListNode(56);
}
}
此时链表的状态如下图:
在代码中加入 :
listNode1.next = listNode2;
listNode2.next = listNode3;
listNode3.next = listNode4;
listNode4.next = listNode5;
链表就会变为下图:
代码中加入:
this.head = listNode1;
所有节点被回收,链表就变成了:
打印val的值:
public void display() {
while (this.head != null) {
System.out.print(this.head.val + " ");
this.head = this.head.next;
}
}
class ListNode {
public int val;
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
public class MyLinkedList {
public ListNode head;//链表的头引用
public void creatList() {
ListNode listNode1 = new ListNode(12);
ListNode listNode2 = new ListNode(23);
ListNode listNode3 = new ListNode(34);
ListNode listNode4 = new ListNode(45);
ListNode listNode5 = new ListNode(56);
listNode1.next = listNode2;
listNode2.next = listNode3;
listNode3.next = listNode4;
listNode4.next = listNode5;
this.head = listNode1;
}
public void display() {
ListNode cur = this.head;
while (cur!= null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
}
public class TestDemo {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.creatList();
myLinkedList.display();
}
}
3.无头单向非循环链表的实现
1.查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
public class TestDemo {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.creatList();
myLinkedList.display();
boolean flg = myLinkedList.contains(34);
System.out.println(flg);
}
}
2.得到单链表的长度
public int size() {
int count = 0;
ListNode cur = this.head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
public class TestDemo {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.creatList();
myLinkedList.display();
boolean flg = myLinkedList.contains(34);
System.out.println(myLinkedList.size());
}
}
3.头插法
public void addFirst(int data) {
ListNode node = new ListNode(data);
if(this.head == null) {
this.head = node;
}else {
node.next = this.head;
this.head = node;
}
}
public class TestDemo {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(12);
myLinkedList.addFirst(23);
myLinkedList.addFirst(34);
myLinkedList.addFirst(45);
myLinkedList.addFirst(56);
myLinkedList.display();
}
}
4.尾插法
public void addLast(int data) {
ListNode node = new ListNode(data);
if(this.head == null) {
this.head = node;
}else {
ListNode cur = this.head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
}
public class TestDemo {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addLast(12);
myLinkedList.addLast(23);
myLinkedList.addLast(34);
myLinkedList.addLast(45);
myLinkedList.addLast(56);
myLinkedList.display();
}
}
5.任意位置插入,第一个数据节点为0号下标
具体思路如下图所示:
public void addIndex(int index,int data) {
if(index < 0 || index > size()) {
System.out.println("index位置不合法!");
return;
}
if(index == 0) {
addFirst(data);
return;
}
if (index == size() ) {
addLast(data);
return;
}
ListNode cur = findIndex(index);
ListNode node = new ListNode(data);
node.next = cur.next;
cur.next = node;
}
public class TestDemo {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.creatList();
myLinkedList.addIndex(1,99);
myLinkedList.display();
}
}
6.删除第一次出现关键字为key的节点
具体思路如下:
1.判断头结点
this.head.val == key;
2.放在中间时
cur.next.val == key;
3.放在末尾时进行判断
while(cur.next != null)
/**
* 找到要删除关键字的前驱
* @param key
* @return
*/
public ListNode searchPerv(int key) {
ListNode cur = this.head;
while (cur.next != null) {
if(cur.next.val == key) {
return cur;
}
cur = cur.next;;
}
return null;
}
//删除第一次出现关键字为key的节点
public void remove(int key) {
if (this.head == null) {
System.out.println("单链表为空,不能删除");
return;
}
if(this.head.val == key) {
this.head = this.head.next;
return;
}
ListNode cur = searchPerv(key);
if (cur == null) {
System.out.println("没有你要删除的节点");
return;
}
ListNode del = cur.next;
cur.next = del.next;
}
public class TestDemo {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.creatList();
myLinkedList.display();
myLinkedList.remove(12);
myLinkedList.display();
myLinkedList.remove(56);
myLinkedList.display();
myLinkedList.remove(34);
myLinkedList.display();
}
}
7.删除所有值为key的节点
//删除所有值为key的节点
public ListNode removeAllKey(int key) {
if(this.head == null) {
return null;
}
ListNode prev = this.head;
ListNode cur = this.head.next;
while (cur != null) {
if(cur.val == key) {
prev.next = cur.next;
cur = cur.next;
}else {
prev = cur;
cur = cur.next;
}
}
//最后处理头
if (this.head.val == key) {
this.head = this.head.next;
}
return this.head;
}
注意:头结点的处理方式就是在全部删除完毕的时候进行判断,然后再进行删除。
8.清除链表
public void clear() {
//this.head == null;
while (this.head != null) {
ListNode curNext = head.next;
this.head.next = null;
this.head = curNext;
}
}
4.无头双向链表
5.无头双向链表的实现
public class MyLinkedList {
static class ListNode {
public int val;
public ListNode prev;//前驱
public ListNode next;//后继
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;
public ListNode last;
}
1.头插法
public void addFirst(int data){
ListNode node = new ListNode(data);
if (head == null) {
head = node;
last = node;
}else {
node.next = head;
head.prev = node;
head = node;
}
}
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.addFirst(2);
myLinkedList.addFirst(3);
myLinkedList.addFirst(4);
myLinkedList.addFirst(5);
myLinkedList.display();
}
}
2.尾插法
注意事项:相比于无头单向链表多了尾结点last,相对应的时间复杂度变为了O(1).
public void addLast(int data){
ListNode node = new ListNode(data);
if (head == null) {
head = node;
last = node;
}else {
last.next = node;
node.prev = last;
last = node;
}
}
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.addFirst(2);
myLinkedList.addFirst(3);
myLinkedList.addFirst(4);
myLinkedList.addFirst(5);
myLinkedList.display();
myLinkedList.addLast(0);
myLinkedList.display();
}
}
3.任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
if (index < 0 || index > size()) {
throw new ListIndexOutOfException("插入位置错误!");
}
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
ListNode cur =findIndex(index);
ListNode node = new ListNode(data);
node.next = cur;
cur.prev.next = node;
node.prev = cur.prev;
cur.prev = node;
}
private ListNode findIndex(int index) {
ListNode cur = head;
while (index != 0) {
cur = cur.next;
index--;
}
return cur;
}
public class ListIndexOutOfException extends RuntimeException{
public ListIndexOutOfException() {
}
public ListIndexOutOfException(String message) {
super(message);
}
}
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.addFirst(2);
myLinkedList.addFirst(3);
myLinkedList.addFirst(4);
myLinkedList.addFirst(5);
myLinkedList.display();
myLinkedList.addIndex(6,3);
}
}
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.addFirst(2);
myLinkedList.addFirst(3);
myLinkedList.addFirst(4);
myLinkedList.addFirst(5);
myLinkedList.display();
myLinkedList.addIndex(2,3);
myLinkedList.display();
}
}
4.查找是否包含关键字key是否在链表当中
public boolean contains(int key){
ListNode cur = this.head;
while (cur != null) {
if(cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.addFirst(2);
myLinkedList.addFirst(3);
myLinkedList.addFirst(4);
myLinkedList.addFirst(5);
myLinkedList.display();
System.out.println(myLinkedList.contains(3));
System.out.println(myLinkedList.contains(55));
}
}
5.删除第一次出现关键字为key的节点
public void remove(int key){
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
//1.删除的是头结点
if (cur == head) {
head = head.next;
//只有一个节点
if (head != null) {
head.prev = null;
}
}else {
//中间 尾巴
cur.prev.next = cur.next;
//不是尾巴节点
if(cur.next != null) {
cur.next.prev = cur.prev;
}else {
//是尾巴节点
last = last.prev;
}
}
return;
}
cur = cur.next;
}
}
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.addFirst(2);
myLinkedList.addFirst(3);
myLinkedList.addFirst(4);
myLinkedList.addFirst(5);
myLinkedList.display();
myLinkedList.remove(3);
myLinkedList.display();
}
}
6.删除所有值为key的节点
public void removeAllKey(int key){
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
//1.删除的是头结点
if (cur == head) {
head = head.next;
//只有一个节点
if (head != null) {
head.prev = null;
}
}else {
//中间 尾巴
cur.prev.next = cur.next;
//不是尾巴节点
if(cur.next != null) {
cur.next.prev = cur.prev;
}else {
//是尾巴节点
last = last.prev;
}
}
}
cur = cur.next;
}
}
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.addFirst(2);
myLinkedList.addFirst(3);
myLinkedList.addFirst(3);
myLinkedList.addFirst(5);
myLinkedList.display();
myLinkedList.removeAllKey(3);
myLinkedList.display();
}
}
7.得到链表的长度
public int size(){
int len = 0;
ListNode cur = head;
while (cur != null) {
len++;
cur = cur.next;
}
return len;
}
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.addFirst(2);
myLinkedList.addFirst(3);
myLinkedList.addFirst(4);
myLinkedList.addFirst(5);
myLinkedList.display();
System.out.println(myLinkedList.size());
}
}
8.打印链表
public void display(){
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.addFirst(2);
myLinkedList.addFirst(3);
myLinkedList.addFirst(4);
myLinkedList.addFirst(5);
myLinkedList.display();
}
}
9.清除链表
public void clear() {
ListNode cur = head;
while (cur != null) {
ListNode curNext = head.next;
cur.prev = null;
cur.next = null;
cur = curNext;
}
head = null;
last = null;
}
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.addFirst(2);
myLinkedList.addFirst(3);
myLinkedList.addFirst(4);
myLinkedList.addFirst(5);
myLinkedList.display();
myLinkedList.clear();
myLinkedList.display();
}
}
6.LinkedList的遍历
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(1); // add(elem): 表示尾插
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
System.out.println(list.size());
// foreach遍历
for (int e:list) {
System.out.print(e + " ");
}
System.out.println();
// 使用迭代器遍历---正向遍历
ListIterator<Integer> it = list.listIterator();
while(it.hasNext()){
System.out.print(it.next()+ " ");
}
System.out.println();
// 使用反向迭代器---反向遍历
ListIterator<Integer> rit = list.listIterator(list.size());
while (rit.hasPrevious()){
System.out.print(rit.previous() +" ");
}
System.out.println();
}
4.顺序表和链表的区别和联系
顺序表:一白遮百丑
白:空间连续、支持随机访问。
丑:1.中间或前面部分的插入删除时间复杂度O(N) 2.增容的代价比较大。
链表:一(胖黑)毁所有
胖黑:以节点为单位存储,不支持随机访问。
所有:1.任意位置插入删除时间复杂度为O(1) 2.没有增容问题,插入一个开辟一个空间。