线性表概念
线性表是一种简单的线性结构。主要操作特点是可以在任意位置插入和删除一个数据元素。除了第一个和最后一个元素,中间的每个元素只有一个前驱元素和一个后继元素。线性表有两种存储方式:第一种是顺序存储结构,第二种是链式存储结构。
顺序表
一、概念
顺序存储结构的线性表称作顺序表。实现顺序存储结构的方法是使用数组。数组把线性表的元素存储在一块连续地址空间的内存单元,这样,线性表中的逻辑上相邻的元素在物理存储地址上也相邻。元素间逻辑上的前驱、后继逻辑关系就表现在元素所处内存单元的物理前、后位置关系上。
二、 存储结构
顺序表实现Iterable接口是因为:
Iterable是一个可迭代接口,与之前版本相比,增加了forEach迭代和获取Spliterator方法。
Iterable提供获取Iterator迭代器方法,用以支持集合遍历。
Iterable提供获取Spliterator可分割迭代器方法,用以支持集合的并发遍历。
顺序表中的T[] eles表示存储元素的数组;N表示顺序表中的元素个数
public class SequenceList<T> implements Iterable<T>{
private T[] eles;
private int N;
@Override
public Iterator<T> iterator() {
return new SIterator();
}
public class SIterator implements Iterator{
private int cusor;
public SIterator(){
this.cusor = 0;
}
//判断是否还有元素可以遍历
@Override
public boolean hasNext() {
return cusor < N;
}
@Override
public Object next() {
return eles[cusor++];
}
}
}
三、操作实现
①初始化SequenceList<T>
public SequenceList(int capacity){
this.eles = (T[])new Object[capacity]; //有参构造初始化传入数组长度
this.N = 0; //初始化元素个数
}
②判断顺序表是否为空
public boolean isEmpty(){
return this.N == 0; //判断元素个数是否为0
}
③将顺序表置为空表
public void clear(){
this.N = 0; //将元素个数置为0
}
④获取顺序表长度
public int length(){
return N;
}
⑤获取指定位置的元素
public T get(int i){
return eles[i]; //获取当前数组下标的元素
}
⑥表的扩容
public void resize(int newSize){
//定义一个临时数组,指向原数组
T[] temp = eles;
//创建新数组
eles = (T[])new Object[newSize];
//把原数组的数据拷贝到原数组即可
for(int i = 0;i < N;i++){
eles[i] = temp[i];
}
}
⑦插入元素
//向表末尾添加元素t
public void insert(T t){
//如果元素个数与数组长度相同就扩容成原数组的两倍
if(N == eles.length) resize(2 * eles.length);
eles[N++] = t;
}
//在指定位置添加元素t
public void insert(int i,T t){
if(N == eles.length) resize(2 * eles.length);
//先将i索引处的元素及其后面的元素依次向后移动一位
for(int index = N;index > i;index--) {
eles[index] = eles[index - 1];
}
eles[i] = t;
N++;
}
⑧删除元素
public T remove(int i){
//记录索引i处的值
T current = eles[i];
//索引i后面元素依次向前移动一位即可
for(int index = i;index < N - 1;index++){
eles[index] = eles[index + 1];
}
N--;
//如果数组元素个数小于数组长度的1/4就将数组长度缩容一半
if(N < eles.length/4) resize(eles.length/2);
return current;
}
⑨返回元素第一次出现索引下标
public int indexOf(T t){
for(int i = 0;i < N;i++){
if(eles[i].equals(t)) return i;
}
//未找到就返回-1
return -1;
}
链表
一、概念
顺序表的链式存储结构也称为链表。链表存储元素的方法是,把存储有元素的结点用指针域构造成链。当然Java已经没有了C中的指针概念,以下我们采用"next"表示指向的下一个结点元素。这里简单介绍一下:指针是指向物理内存单元地址的变量。我们把一个由元素域及一个或若干个指针域组成的结构体称为一个结点。
二、单向链表
由多个结点组成,每个结点都由一个数据域和一个指针域组成。头部是没有数据的,数据域用来存储数据,指针域用来指向后继结点。链表的头结点的指针域指向第一个真正存储数据的结点。这里讨论的是带头结点的单向链表。
一、存储结构
//在Java的集合中链表的类是LinkedList,这里是没和它重名的类
public class LinkList<T> implements Iterable<T>{
private Node head;//记录头结点
private int N; //记录链表的长度
//结点类
private class Node{
T data;//存储数据
Node next;//指向下一个结点
public Node(T data,Node next){
this.data = data;
this.next = next;
}
}
@Override
public Iterator<T> iterator() {
return new LIterator();
}
public class LIterator implements Iterator{
private Node n;
public LIterator(){
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.data;
}
}
}
二、操作实现
①单向链表的初始化
public LinkList(){
this.head = new Node(null,null); //初始化头结点
this.N = 0; //初始化单向链表元素个数
}
②判断单向链表是否为空
public boolean isEmpty(){
return N == 0; //判断表中元素个数是否为0
}
③清空链表
public void clear(){
head.next = null; //将头结点的指针域置为null
this.N = 0; //将链表元素个数置为0
}
④获取单向链表长度
public int length(){
return N; //返回元素个数
}
⑤获取表中第i个元素值
public T get(int i){
//从头结点指向的下一个元素开始找
Node n = head.next;
for(int index = 0;index < i;index++){
n = n.next;
}
return n.item;
}
⑥添加元素t
//尾插法
public void insert(T t){
//创建新结点,保存元素t
Node newNode = new Node(t,null);
//找到当前最后一个结点
Node n = head;
while(n.next != null){
n = n.next;
}
//让当前最后一个结点指向新结点
n.next = newNode;
N++;
}
//向指定位置i处添加元素
public void insert(int i,T t){
//找到i位置前一个结点
Node preNode = head;
for(int index = 0;index < i;index++){
preNode = preNode.next;
}
//找到i位置的结点
Node currentNode = preNode.next;
//创建新结点,并且新结点需要指向原来i位置的节点
Node newNode = new Node(t,currentNode);
//原来i位置的前一个结点指向新结点即可
preNode.next = newNode;
N++;
}
⑦删除元素
public T remove(int i){
//找到i位置前一个结点
Node preNode = head;
for(int index = 0;index < i;index++){
preNode = preNode.next;
}
//找到i位置的节点
Node currentNode = preNode.next;
//找到i位置后一个结点
Node nextNode = currentNode.next;
//前一个结点指向下一个结点
preNode.next = nextNode;
N--;
return currentNode.item;
}
⑧查找元素t在链表第一次出现的位置
public int indexOf(T t){
//从头结点开始,依次找到每一个结点,取出item数据和t比较,如果相同就找到了
Node n = head;
for(int i = 0;n.next != null;i++){
n = n.next;
if(n.data.equals(t)) return i;
}
return -1;
}
⑨反转链表
public void reverse(){
//如果为空链表直接返回即可
if(isEmpty()) return;
reverse(head.next);
}
//反转指定的结点curr,并把反转后的结点返回
public Node reverse(Node curr){
if(curr.next == null){
head.next = curr;
return curr;
}
//递归的反转当前结点curr的下一个结点:返回值就是链表反转后,当前结点的下一个结点
Node preNode = reverse(curr.next);
preNode.next = curr;
curr.next = null;
return curr;
}
三、双向链表
每个结点除后继指针域还有一个前驱指针域。和单向链表类同。链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点。双向链表有分循环和非循环两种结构。这里讨论的是带头结点的双向不循环链表。
一、存储结构
public class TwoWayLinkList<T> implements Iterable<T>{
private Node head;//首结点
private Node last;//最后一个结点
private int N;//链表的元素个数
private class Node{
public T data;//存储数据
public Node pre;//指向上一个结点
public Node next;//指向下一个结点
public Node(T data,Node pre,Node next){
this.data = data;
this.pre = pre;
this.next = next;
}
}
@Override
public Iterator<T> iterator() {
return new TIterator();
}
public class TIterator implements Iterator{
private Node n;
public TIterator(){
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.data;
}
}
}
二、操作实现
①初始化双向链表
public TwoWayLinkList(){
this.head = new Node(null,null,null); //初始化头结点
this.last = null; //初始化尾结点
this.N = 0; //初始化双向链表元素个数
}
②判断双向链表是否为空
public boolean isEmpty(){
return N == 0;
}
③清空链表
public void clear(){
this.head.next = null;
this.head.pre = null;
this.head.data = null;
this.last = null;
this.N = 0;
}
④获取链表长度
public int length(){
return N;
}
⑤获取第一个元素
public T getFirst(){
if(isEmpty()) return null;
return head.next.data;
}
⑥获取最后一个元素
public T getLast(){
if(isEmpty()) return null;
return last.data;
}
⑦获取指定位置i处元素
public T get(int i){
Node n = head.next;
for(int index = 0;index < i;index++){
n = n.next;
}
return n.data;
}
⑧获取元素t第一次出现位置
public int indexOf(T t){
Node n = head;
for(int i = 0;n.next != null;i++){
n = n.next;
if(n.next.equals(t)) return i;
}
return -1;
}
⑨添加元素t
public void insert(T t){
if(isEmpty()){
//创建新的结点
Node newNode = new Node(t,head,null);
//让新结点称为尾结点
last = newNode;
//让头结点指向尾结点
head.next = last;
}else {
//创建新的结点
Node newNode = new Node(t,last,null);
//让当前的尾结点指向新结点
last.next = newNode;
//让新结点称为尾结点
last = newNode;
}
N++;
}
//在指定位置i处插入元素t
public void insert(int i,T t){
//找到i位置的前一个结点
Node preNode = head;
for(int index = 0;index < i;index++){
preNode = preNode.next;
}
//找到i位置的结点
Node currentNode = preNode.next;
//创建新结点
Node newNode = new Node(t,preNode,currentNode);
//让i位置的前一个结点的下一个结点变为新结点(也就是当前结点)
preNode.next = newNode;
//让i位置的前一个结点变为新结点
currentNode.pre = newNode;
N++;
}
⑩删除元素并返回值
public T remove(int i){
//找到i位置的前一个结点
Node preNode = head;
for(int index = 0;index < i;index++){
preNode = preNode.next;
}
//找到i位置的结点
Node currentNode = preNode.next;
//找到i位置的下一个结点
Node nextNode = currentNode.next;
//让i位置的前一个结点的下一个结点变为i位置的下一个结点
preNode.next = nextNode;
//让i位置的下一个结点的上一个结点变为i位置的前一个结点
currentNode.pre = preNode;
N--;
return currentNode.data;
}