数据结构基础
目录
数据结构基础
线性表
顺序表
链表
顺序表和链表的区别:
栈
队列
线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表中的元素个数就是线性表的长度,表的起始位置称为表头,结束位置称为表尾,当一个线性表中没有元素时称为空表。
线性表是一种在实际中广泛使用的数据结 构,常见的线性表:顺序表、链表、栈、队列...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
我们存放数据还是使用数组,但是为其编写一些额外的操作来强化为线性表,像这样底层依然采用顺序存储实现的线性表称为顺序表:
public class ArrayList <E>{ //泛型E,因为表中要存的具体数据类型待定
private int size=0; //当前已经存放的元素数量
private int capacity=10; //当前顺序表的容量
private Object[]array=new Object[capacity]; //底层存放的数组
}
当插入元素时需要将插入位置腾出来,也就是将后面所有元素向后移;
如果要删除元素,需要将所有元素向前移动。
顺序表是紧凑的,不能出现空位。
插入方法(把元素放入顺序表):
public class ArrayList <E>{
private int size=0;
private int capacity=10;
private Object[]array=new Object[capacity];
public void add(E element,int index){ //插入方法需要支持在指定下标位置插入
if(index<0||index>size)
throw new IndexOutOfBoundsException("插入位置非法,合法的插入位置为:0-"+size);
//在插入前判断,允许插入的位置只有[0,size]
for (int i = size; i >index ; i--) //从后往前一个一个搬运元素
array[i]=array[i-1];
array[index]=element; //腾出位置后插入元素放到对应的位置上
size++; //插入完成后将size自增(扩大)
}
//重写toString方法打印当前存放的元素
public String toString(){
StringBuilder builder=new StringBuilder();
for (int i = 0; i < size; i++) builder.append(array[i]).append(" ");
return builder.toString();
}
}
public class Main {
public static void main(String[] args) {
ArrayList<String>list=new ArrayList<>();
list.add("aaa",0);
list.add("bbb",1);
System.out.println(list);
}
}
//输出aaa bbb
扩容操作:
public void add(E element,int index){
if(index<0||index>size)
throw new IndexOutOfBoundsException("插入位置非法,合法的插入位置为:0-"+size);
if(size>=capacity) {
int newCapacity=capacity+(capacity>>1); //原本容量的1.5倍
Object[]newArray=new Object[newCapacity]; //创建一个新数组来存放更多的元素
System.arraycopy(array,0,newArray,0,size); //使用arraycopy快递拷贝原数组内容到新的数组
array=newArray; //更换为新的数组
capacity=newCapacity; //容量变成扩容之后的
}
删除操作:
可删除的范围只可能是[0,size)
@SuppressWarnings("unchecked") //屏蔽未经检查警告
public E remove(int index){ //删除对应位置上的元素,注意需要返回被删除的元素
if(index<0||index>size-1)
throw new IndexOutOfBoundsException("删除位置非法,合法的删除位置为:0-"+(size-1));
E e=(E)array[index]; //因为存放的是Object类型,需要强制类型转换为E
for (int i = index; i <size ; i++) //从前往后,挨个往前搬一位
array[i]=array[i+1];
size--;
return e;
}
获取指定下标位置上的元素:
@SuppressWarnings("unchecked")
public E get(int index){
if(index<0||index>size-1)
throw new IndexOutOfBoundsException("查询位置非法,合法位置为:0"+(size-1));
return (E) array[index];
}
判断某位置是否为空:
public boolean isEmpty(){
return size==0;
}
System.out.println(list.get(2));
链表
顺序表的缺陷:
ArrayList底层使用数组来存储元素:由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java 集合中又引入了LinkedList,即链表结构。
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。在逻辑结构上连续,但在物理上不一定连续。
实际中链表的结构非常多样:
1. 单向或者双向
2. 带头或者不带头
3. 循环或者非循环
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。这种结构在笔试面试中出现很多。
无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。
带头单向链表:
public class LinkedList <E>{
//链表的头结点,用于连接之后的所有结点
private final Node<E>head=new Node<>(null);
private int size; //存当前元素数量,方便后续操作
private static class Node<E>{ //结点类,仅供内部使用
private E element; //每个结点都存放元素
private Node<E> next; //以及指向下一个结点的引用
public Node(E e){
this.element=e;
}
}
}
链表的插入:
public void add(E element,int index){
if(index<0||index>size)
throw new IndexOutOfBoundsException("插入位置非法,合法的插入位置为:0-"+size);
//先找到对应位置的前驱结点
Node<E>prev=head;
for (int i = 0; i < index; i++)
prev =prev.next;
Node<E>node=new Node<>(element); //创建新的结点
node.next=prev.next; //新的结点指向原本在这个位置上的结点
prev.next=node; //让前驱结点指向当前结点
size++; //更新size
}
toString方法:
@Override
public String toString(){
StringBuilder builder=new StringBuilder();
Node<E> node=head.next; //从第一个结点开始,一个一个遍历,遍历一个就拼接到字符串上去
while (node!=null){
builder.append(node.element).append(" ");
node=node.next;
}
return builder.toString();
}
删除操作:
public E remove(int index){
if(index<0||index>size-1)
throw new IndexOutOfBoundsException("删除位置非法,合法的删除位置为:0-"+(size-1));
Node<E>prev=head;
for (int i = 0; i < index; i++) //找到前驱结点
prev=prev.next;
E e=prev.next.element; //先把待删除结点存放的元素取出来
prev.next=prev.next.next;
size--;
return e;
}
获取对应位置上的元素: (有点蒙,做个标记)
public E get(int index){
if(index<0||index>size-1)
throw new IndexOutOfBoundsException("查询位置非法,合法位置为:0"+(size-1));
Node<E>node=head;
while (index-->=0) //这里直接让index减到-1为止
node=node.next;
return node.element;
}
public int size(){
return size;
}
顺序表和链表的区别:
不同点 | ArrayList | LinkedList |
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持O(1) | 不支持:O(N) |
头插 | 需要搬移元素,效率低O(N) | 只需修改引用的指向,时间复杂度为O(1) |
插入 | 空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |
栈
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。
栈可以使用顺序表实现,也可以使用链表实现,使用链表会更加方便。我们可以直接将头结点指向栈顶结点,而栈顶结点连接后续的栈内结点。
入栈操作:
当有新的元素入栈,只需要在链表头部插入新的结点即可。
public class LinkedStack <E>{
private final Node<E> head=new Node<>(null);
public void push(E element){
Node<E>node=new Node<>(element); //直接创建新结点
node.next=head.next; //新结点的下一个变成原本的栈顶结点
head.next=node; //头结点的下一个改成新的结点
}
public boolean isEmpty(){
return head.next==null;
}
private static class Node<E>{
private E element;
private Node<E> next;
public Node(E e){
this.element=e;
}
}
}
出栈操作:
public E pop(){
if(isEmpty()){ //如果栈没有元素了无法取
throw new NoSuchElementException("栈为空");
}
E e=head.next.element; //先把待出栈元素取出来
head.next=head.next.next; //直接让头结点的下一个指向下一个的下一个
return e;
}
public static void main(String[] args) {
LinkedStack<String>stack=new LinkedStack<>();
stack.push("A");
stack.push("B");
stack.push("C");
stack.push("D");
while (!stack.isEmpty()){
System.out.println(stack.pop());
}
}
//输出
D
C
B
A
队列
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾(Tail/Rear)
出队列:进行删除操作的一端称为队头 (Head/Front)
当有新的元素入队时,只需要拼在队尾就可以了,同时队尾指针也要后移一位。
出队时,只需要移除队首指向的下一个元素即可。
public class LinkedQuene <E>{
private final Node<E> head=new Node<>(null);
//入队操作
public void offer(E element){
Node<E>tail=head;
while (tail.next!=null) //入队直接放到最后一个结点的后面
tail=tail.next;
tail.next=new Node<>(element);
}
//出队操作
public E poil(){
if(isEmpty()) //若队列没有元素了无法取出
throw new NoSuchElementException("队列为空");
E e=head.next.element;
head.next=head.next.next; //直接从队首取出
return e;
}
//仅获取不移除队首
public E peak(){
if(isEmpty())
throw new NoSuchElementException("队列为空");
return head.next.element;
}
public boolean isEmpty(){
return head.next==null;
}
private static class Node<E>{
private E element;
private Node<E> next;
public Node(E e){
this.element=e;
}
}
}