1. 线性表
- 线性表(linear list)是n个具有相同特性的数据元素的有限序列。
- 线性表是一种在实际中广泛使用的数据结构,常见 的线性表:顺序表、链表、栈、队列、字符串…
- 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2. 顺序表
2.1 概念及结构
- 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储。
- 动态顺序表:使用动态开辟的数组存储。
- 静态顺序表适用于确定知道需要存多少数据的场景.
- 静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用.
- 相比之下动态顺序表更灵活, 根据需要动态的分配空间大小.
2.2 接口实现
我们来实现一个动态顺序表, 以下是需要支持的接口:
2.3 顺序表的问题及思考
1. 顺序表中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
//枳洛淮南
import java.util.Arrays;
//顺序表
public class sequenceList
{
public int[] elem; //默认null
public int usedSize; //默认0,可用空间
//构造函数1
public sequenceList(int _size)
{
this.elem = new int[_size];
}
//构造函数2
public sequenceList()
{
this.elem = new int[10];
}
public static void main(String[] args)
{
sequenceList seq1 = new sequenceList(5); //默认为10个元素
seq1.add(0, 1);
seq1.add(1, 2);
seq1.add(2, 3);
seq1.add(3, 4);
seq1.add(4, 5);
seq1.add(5, 6);
seq1.display();
System.out.println(seq1.contains(5)); //true
System.out.println(seq1.contains(10000)); //false
System.out.println(seq1.search(2)); //1
System.out.println(seq1.search(4)); //3
System.out.println(seq1.search(8888)); //-1
System.out.println(seq1.getPos(4)); //5
System.out.println(seq1.getPos(100)); //非法
seq1.remove(1);
seq1.display();
seq1.remove(3);
seq1.display();
seq1.remove(5);
seq1.display();
seq1.remove(999);
}
// 打印顺序表
public void display()
{
for (int i = 0; i < this.usedSize; i++)
{
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
// 在 pos 位置新增元素,若pos有元素则后移该位置原数据
public void add(int pos, int data)
{
//下标位置不能小于0或大于顺序表的最大有效位置
if (pos < 0 || pos > this.usedSize)
{
System.out.println("输入位置非法");
return;
}
int i = this.usedSize - 1;
//如果可用空间等于顺序表有用空间
if (this.usedSize == this.elem.length) //扩容
{
this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
}
while (i >= pos)
{
this.elem[i + 1] = this.elem[i];
i--;
}
this.elem[pos] = data;
this.usedSize++;
}
// 判定是否包含某个元素
public boolean contains(int toFind)
{
for (int i = 0; i < this.usedSize; i++)
{
if (this.elem[i] == toFind)
{
return true;
}
}
return false;
}
// 查找某个元素对应的位置(有多个则返回第一个的位置)
public int search(int toFind)
{
for (int i = 0; i < this.usedSize; i++)
{
if (this.elem[i] == toFind)
{
return i;
}
}
//没有则返回-1
return -1;
}
// 获取 pos 位置的元素(判断pos是否合法)
public int getPos(int pos)
{
if (pos < 0 || pos >= this.usedSize)
{
System.out.print("输入位置非法\t");
return -1;
}
return this.elem[pos];
}
// 给 pos 位置的元素设为 value
public void setPos(int pos, int value)
{
if (pos < 0 || pos >= this.usedSize)
{
System.out.print("输入位置非法\t");
return;
}
this.elem[pos] = value;
}
//删除第一次出现的关键字key
public void remove(int toRemove)
{
//先判断是否有该数字
int index = this.search(toRemove);
if (-1 == index)
{
System.out.println("没有该数字,无法删除");
} else
{
for (int i = index; i < this.usedSize - 1; i++)
{
this.elem[i] = this.elem[i + 1];
}
//if (this.usedSize - 1 - index >= 0)
// System.arraycopy(this.elem, index + 1, this.elem, index, this.usedSize - 1 - index);
this.usedSize--;
}
}
// 获取顺序表长度
public int size()
{
return this.usedSize;
}
// 清空顺序表
public void clear()
{
// for (int i = 0;i < this.usedSize;i++)
// {
// //this.elem[i] = null; //引用类型必须设置为空
// }
this.usedSize = 0;
}
}
3. 链表
3.1 链表的概念及结构
- 链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构: - 单向、双向
- 带头、不带头
- 循环、非循环
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构。
- 无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。
3.2 链表的实现
class Node
{
public int data; //数据域
public Node next; //地址域 --> 默认为空
public Node(int _data)
{
this.data = _data;
} //结点
}
//枳洛淮南
//单向不带头非循环链表
public class LinkedListDemo
{
//单链表的最后一个节点的next域为null
public Node head;
//头插
public void addFirst(int _value)
{
Node node = new Node(_value);
//本来没有结点,即空链表
if (this.head != null)
{
node.next = this.head;
}
this.head = node;
}
//尾插
public void addLast(int _value)
{
Node node = new Node(_value);
if (this.head.next == null) //第一次插入
{
this.head = node;
} else//不是第一次插入
{
Node cur = this.head;
while (cur.next != null)
{
cur = cur.next;
}
cur.next = node;
}
}
//求单链表长度
public int getLength()
{
int length = 0;
if (this.head.next != null)
{
Node cur = this.head;
while (cur != null)
{
length++;
cur = cur.next;
}
}
return length;
}
//检查下标合法性
public boolean checkIndex(int index)
{
if (index < 0 || index > this.getLength())
{
System.out.println("下标不合法!");
return false;
}
return true;
}
//按下标插入
public void addIndex(int index, int _value)
{
if (!this.checkIndex(index))
{
return;
}
int useSize = this.getLength(); //可用空间
if (index == 0) //插入位置我第一项,直接头插
{
addFirst(_value);
return;
} //插入位置为最后一项,尾插
if (index == useSize)
{
addLast(_value);
return;
}
//寻找前驱
Node cur = searchPrevIndex(index);
Node node = new Node(_value);
node.next = cur.next;
cur.next = node;
}
//查找index-1的位置,并返回引用
public Node searchPrevIndex(int index)
{
Node cur = this.head;
int count = 0;
while (count < index - 1)
{
cur = cur.next;
count++;
}
return cur;
}
//打印
public void display()
{
Node cur = this.head;
while (cur != null)
{
System.out.print(cur.data + " -> ");
cur = cur.next;
}
System.out.println("NULL");
}
//按关键字查找
public Node searchPrevKey(int key)
{
if (head.data == key)
{
return head;
}
Node cur = this.head.next;
while (cur != null && cur.next != null)
{
if (cur.data == key)
{
return cur;
}
cur = cur.next;
}
assert cur != null;
if (cur.data == key)
{
return cur;
}
return null;
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key)
{
return this.searchPrevKey(key) != null;
}
//删除所有值为key的节点
public void removeAllKey(int key)
{
if (this.head == null)
{
System.out.println("单链表没有元素");
return;
}
Node cur = this.head;
while (cur.next != null)
{
if (this.contains(key))
{
this.remove(key);
}
cur = cur.next;
}
}
//删除第一次出现的关键字为key的结点
public void remove(int key)
{
if (this.head == null)
{
System.out.println("单链表没有元素");
return;
}
if (this.head.data == key) //头内data为要删除的,头后移
{
head = head.next;
return;
}
//找到节点
if (this.contains(key))
{
Node del = this.head.next;
Node cur = this.head;
while (del.next != null)
{
if (del.data == key)
{
cur.next = del.next;
}
del = del.next;
cur = cur.next;
}
if (del.data == key) //删除最后一个结点
{
cur.next = null;
}
} else
{
System.out.println("单链表没有" + key + "元素!");
}
}
//清空
public void clear()
{
this.head = null;
}
//逆置
public void reverseList()
{
//头插法
// Node cur = this.head;
// Node prev = null;
//
// while(cur != null)
// {
// Node temp = cur.next;
// cur.next = prev;
// prev = cur;
// cur = temp;
// }
// this.head = prev;
//尾插法
Node cur = this.head;
Node prev = null;
while (cur != null)
{
Node temp = cur.next;
if (temp == null)
{
this.head = cur;
}
cur.next = prev;
prev = cur;
cur = temp;
}
}
}
public class TestDemo
{
public static void main(String[] args)
{
LinkedListDemo myLinkedList = new LinkedListDemo();
myLinkedList.addFirst(2);
myLinkedList.addFirst(2);
myLinkedList.addFirst(2);
myLinkedList.addFirst(2);
myLinkedList.addLast(3);
myLinkedList.display();
myLinkedList.removeAllKey(2);
myLinkedList.display();
//头插
myLinkedList.addFirst(5);
myLinkedList.addFirst(4);
myLinkedList.addFirst(3);
myLinkedList.addFirst(2);
myLinkedList.addFirst(1);
//尾插
myLinkedList.display();
myLinkedList.addLast(7);
myLinkedList.addLast(8);
myLinkedList.addLast(100);
myLinkedList.display();
//按位置插入
myLinkedList.addIndex(5, 6);
myLinkedList.addIndex(8, 9);
myLinkedList.display();
//移除
myLinkedList.remove(9);
myLinkedList.remove(1);
myLinkedList.display();
System.out.println(myLinkedList.contains(100));
System.out.println(myLinkedList.contains(123));
myLinkedList.display();
myLinkedList.reverseList();
myLinkedList.display();
myLinkedList.addLast(77777);
myLinkedList.addLast(77777);
myLinkedList.addLast(77777);
myLinkedList.addLast(77777);
myLinkedList.addLast(77777);
myLinkedList.display();
myLinkedList.removeAllKey(77777);
myLinkedList.display();
myLinkedList.removeAllKey(7);
myLinkedList.display();
myLinkedList.removeAllKey(2);
myLinkedList.display();
System.out.println(myLinkedList.head.next);
System.out.println(myLinkedList.head);
System.out.println(myLinkedList.searchPrevKey(2));
System.out.println(myLinkedList.searchPrevKey(3));
System.out.println(myLinkedList.searchPrevKey(4));
System.out.println(myLinkedList.searchPrevKey(9));
System.out.println("\n" + myLinkedList.contains(3));
System.out.println(myLinkedList.contains(6));
System.out.println(myLinkedList.contains(100));
}
}
4. 顺序表和链表的区别和联系
顺序表:一白遮百丑
白:
空间连续、支持随机访问
丑:
1.中间或前面部分的插入删除时间复杂度O(N)
2.增容的代价比较大。
链表:一(胖黑)毁所有
胖黑:以节点为单位存储,不支持随机访问
所有:
1.任意位置插入删除时间复杂度为O(1)
2.没有增容问题,插入一个开辟一个空间。