谈到线性的数据结构,那肯定离不开两个最基础的:数组和链表,当然有了数组和链表就会聊到栈和队列。
那么本篇我们就来介绍数组和链表
一、数组
数组(Array) 是一种很常见的数据结构。它由相同类型的元素(element)组成,并且是使用一块连续的内存来存储。
我们直接可以利用元素的索引(index)可以计算出该元素对应的存储地址。
数组的特点是:提供随机访问 并且容量有限。
假如数组的长度为 n。
访问:O(1)//访问特定位置的元素
插入:O(n )//最坏的情况发生在插入发生在数组的首部并需要移动所有元素时
删除:O(n)//最坏的情况发生在删除数组的开头发生并需要移动第一元素后面所有的元素时
随机访问能力:可以以o(1)时间复杂度,以下标访问某一元素
物理上连续,逻辑上连续,所以数组有下标
数组一旦初始化,长度不能被修改
当需要扩容时,需要创建新数组,将老数组的内容复制过来
在Java中数组是一个对象
优点:可以随机访问
缺点:需要连续的空间
int [] a = new int[10];
了解完数组,肯定少不了arrylist,arrylist以后我会拿出单独的一篇来总结,我继续往下总结。
二、链表
链表(LinkedList) 虽然是一种线性表,但是并不会按线性的顺序存储数据,使用的不是连续的内存空间来存储数据。
链表的插入和删除操作的复杂度为 O(1) ,只需要知道目标位置元素的上一个元素即可。但是,在查找一个节点或者访问特定位置的节点的时候复杂度为 O(n) 。
使用链表结构可以克服数组需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但链表不会节省空间,相比于数组会占用更多的空间,因为链表中每个节点存放的还有指向其他节点的指针。除此之外,链表不具有数组随机读取的优点。
可不断扩容
每个节点由值和next构成
有头插和尾插两种插入方式
假如链表中有n个元素。
访问:O(n)//访问特定位置的元素
插入删除:O(1)//必须要要知道插入元素的位置
构建单链表,完成头插和尾插的写法
/*
构建单链表
完成头插法,尾插法
*/
//listnode代表一个节点
class ListNode{
public int val;
public ListNode next;
//构造函数
public ListNode(int a){
this.val=a;
}
}
public class LinkedList {
public static void main(String[] args) {
LinkedList a = new LinkedList();
a.creatList();
a.addFirst(66);
a.addLast(77);
a.dispaly();
}
//链表的头
public ListNode head;
public void creatList(){
ListNode listNode1 = new ListNode(11);
ListNode listNode2 = new ListNode(22);
ListNode listNode3 = new ListNode(33);
ListNode listNode4 = new ListNode(44);
ListNode listNode5 = new ListNode(55);
this.head = listNode1;
listNode1.next = listNode2;
listNode2.next = listNode3;
listNode3.next = listNode4;
listNode4.next = listNode5;
}
//头插法
public void addFirst(int data){
ListNode node = new ListNode(data);
node.next = this.head;
this.head = node;
}
//尾插法
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 void dispaly(){
ListNode cur = this.head;
while (cur != null){
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
}
常见链表分类:
- 单链表
- 双向链表
- 循环链表
- 双向循环链表
单链表 单向链表只有一个方向,结点只有一个后继指针 next 指向后面的节点。因此,链表这种数据结构通常在物理内存上是不连续的。我们习惯性地把第一个结点叫作头结点,链表通常有一个不保存任何值的 head 节点(头结点),通过头结点我们可以遍历整个链表。尾结点通常指向 null。
循环链表 其实是一种特殊的单链表,和单链表不同的是循环链表的尾结点不是指向 null,而是指向链表的头结点。
双向链表 包含两个指针,一个 prev 指向前一个节点,一个 next 指向后一个节点。
双向循环链表 最后一个节点的 next 指向 head,而 head 的 prev 指向最后一个节点,构成一个环。
- 如果需要支持随机访问的话,链表没办法做到。
- 如果需要存储的数据元素的个数不确定,并且需要经常添加和删除数据的话,使用链表比较合适。
- 如果需要存储的数据元素的个数确定,并且不需要经常添加和删除数据的话,使用数组比较合适。
链表和数组的区别
- 数组支持随机访问,而链表不支持。
- 数组使用的是连续内存空间对 CPU 的缓存机制友好,链表则相反。
- 数组的大小固定,而链表则天然支持动态扩容。如果声明的数组过小,需要另外申请一个更大的内存空间存放数组元素,然后将原数组拷贝进去,这个操作是比较耗时的!
当然讲了链表就会想到linkedlist,以后我也会单独开一篇总结一下。