目录
数据结构概述
数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。数据结构包括三方面的内容:逻辑结构、存储结构和对数据的操作。其中逻辑结构是对数据之间关系的描述,而存储结构是数据的逻辑结构在计算机中具体位置的映像。
按照逻辑结构的不同,数据可划分为线性结构和非线性结构;按照存储方式的不同,数据又被划分为顺序存储、链式存储、索引存储和散列存储。我们主要学习以下几种数据结构:
(1)顺序表
(2)单链表
(3)栈
(4)队列
(5)哈希表
(6)红黑树
1.顺序表
(1)定义
顺序表就是把线性表中的所有元素按照其逻辑顺序,依次存储到从指定的存储位置开始的一块连续的存储空间中。
因为顺序表中每个元素所占用的地址大小是相同的,所以只需要记录表的起始地址,即第一个元素的地址,而其他的元素可通过逻辑索引直接找到其对应的地址,因此顺序表示可以实现随机存取。
但正因为顺序表占用连续的存储空间,因此只能预分配空间,一旦空间分配好以后就不能改变,这就导致了顺序表在元素占满分配空间之后很难再去添加一个新的元素,只能够通过开辟新的存储空间存储所有元素并把替换原来的地址。
(2)声明一个顺序表以及其增删改查操作
/**
* 顺序表
*/
public class SqList<E> {
private Object[] elementsData;//存放顺序表元素的数组
private int size;//存放顺序表元素的个数
//构造方法:数组初始容量为10
public SqList() {
elementsData = new Object[10];
}
//构造方法:指定数组初始容量
public SqList(Integer capacity) {
if (capacity < 0) throw new IllegalArgumentException();
elementsData = new Object[capacity];
}
//添加元素
public void add(E data) {
elementsData = extend();
elementsData[size] = data;
size++;
}
//删除元素:按索引
public E remove(int index) {
E temp = getElement(index);
for (int i = index; i < size - 1; i++) {
elementsData[i] = elementsData[i + 1];
}
elementsData[size - 1] = null;
size--;
return temp;
}
//修改元素:按索引
public E replace(int index, E data) {
E temp = getElement(index);
elementsData[index] = data;
return temp;
}
//查找元素:按索引
public E get(int index) {
return getElement(index);
}
//获取已有元素个数
public int size() {
return size;
}
//私有方法:数组扩容
private Object[] extend() {
//空间足够返回原数组
if (size < elementsData.length) return elementsData;
//容量扩充为两倍
//+1是为了保证数组长度为0时无法扩充
Object[] temp = new Object[size * 2 + 1];
//赋值原数组信息
for (int i = 0; i < size; i++) {
temp[i] = elementsData[i];
}
return temp;
}
//私有方法,获取指定元素
private E getElement(int index) {
if (index < 0 || index >= size) throw new IllegalArgumentException();
return (E) elementsData[index];
}
}
2.单链表
(1)定义
单链表由各个结点之间通过一个 “ 指针 ” 彼此链接起来而组成。每个结点包含两个部分:结点的数据和指向下一个结点的 next 指针(记录下一个结点在内存中的位置)。而在单链表中则只记录头结点的位置和尾结点的位置,并且头结点不存放数据,尾结点指针为 null 。
如图所示,单链表并不需要在内存中预先开辟连续的空间,而是在使用时临时为结点开辟一个空间,并把该结点地址保存在它的前驱结点中。而在遍历整个链表时,只需要知道头结点的地址便可以一直向下遍历直到到达尾结点处。在单链表中保存尾结点地址的好处在于:能够很快的为单链表添加结点。
(2)声明一个单链表以及其增删改查操作
/**
* 单链表
*/
public class LinkList<E> {
/**
* 声明单链表的结点
*/
private class LNode{
private E data;//结点数据
private LNode next;//结点的后继结点地址
//构造方法
private LNode(){}
}
private LNode head;//头结点地址
private LNode tail;//尾结点地址
private int size;//元素个数
//构造方法
public LinkList(){
head = new LNode();//创建一个头结点
tail = head;//尾指针指向头结点
}
//添加元素
public void add(E data){
LNode temp = new LNode();//创建一个新的结点
temp.data = data;//给结点赋值
tail.next = temp;//前驱结点指向该结点
tail = temp;//尾结点指向该结点
size++;
}
//删除元素:按索引
public E remove(int index){
if (index < 0 || index >= size) throw new IllegalArgumentException();
LNode pre = head;//记录前驱结点
LNode now = head.next;//记录当前结点
int i = 0;//记录索引
while (true){
if(i==index){
pre.next = now.next;//前驱结点指向后继结点
break;
}
//记录指针向后移
pre = now;
now = now.next;
i++;
}
size--;
return now.data;
}
//修改元素:按索引
public E replace(int index,E data){
if (index < 0 || index >= size) throw new IllegalArgumentException();
LNode temp = head.next;//记录指针
int i = 0;
while (true){
if(i==index){
E dataTemp = temp.data;//保存当前数据
temp.data = data;//修改数据
return dataTemp;
}
temp = temp.next;//记录指针向后移
i++;
}
}
//查找元素:按索引
public E get(int index){
if (index < 0 || index >= size) throw new IllegalArgumentException();
LNode temp = head.next;//记录指针
int i = 0;
while (true){
if(i==index){
return temp.data;
}
temp = temp.next;//记录指针向后移
i++;
}
}
//获取已有元素个数
public int size() {
return size;
}
}
链表的种类有很多,除了单链表以外还有双链表、循环链表、静态链表等,但这些都是以单链表为基础的,是单链表的优化和补充。比如双链表,就是在单链表的基础上在每个结点中除后继结点外额外记录了此结点的前驱结点,即双向链表;而循环链表则是把单链表的尾结点指针指向了链表第一个结点(不是头结点),这些均是对单链表功能缺陷的补充,在此不再做详细介绍。