线性表
概述
- 线性表是最基本、最简单、也是最常用的一种数据结构。
- 一个线性表是n个具有相同特性的数据元素的有限序列。
先驱元素: 若A元素在B元素的前面,则称A为B的前驱元素
后继元素: 若B元素在A元素的后面,则称B为A的后继元素
线性表的特征: 数据元素之间具有一种“一对一”的逻辑关系。
- 第一个数据元素没有前驱,这个数据元素被称为头结点;
- 最后一个数据元素没有后继,这个数据元素被称为尾结点;
- 除了第一个和最后一个数据元素外,其他数据元素有且仅有一个前驱和一个后继
线性表的分类:
线性表中数据存储的方式可以是顺序存储,也可以是链式存储,按照数据的存储方式不同,可以把线性表分为顺序
表和链表。
顺序表
概述:
- 顺序表是在计算机内存中以数组的形式保存的线性表
- 线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中再逻辑结构上响铃的数据元素存储在相邻的物理存储单元中
- 即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。
顺序表API设计
顺序表类名SequenceList:
- 构造方法:SequenceList(int capacity):创建容量为capacity的SequenceList对象
- 成员变量:
1.private T[] eles:存储元素的数组
2.private int N:当前线性表的长度 - 成员方法
1.public void clear():空置线性表
2.publicboolean isEmpty():判断线性表是否为空,是返回true,否返回false
3.public int length():获取线性表中元素的个数
4.public T get(int i):读取并返回线性表中的第i个元素的值
5.public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素。
6.public void insert(T t):向线性表中添加一个元素t
7.public T remove(int i):删除并返回线性表中第i个数据元素。
8.public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返
回-1.
顺序表的代码实现
import java.util.Iterator;
public class SequenceList<T> implements Iterable<T>{
//存储元素的数组
private T[] eles;
//记录当前顺序表中的元素个数
private int N;
//构造方法
public SequenceList(int capacity){
//初始化数组
this.eles=(T[])new Object[capacity];
//初始化长度
this.N=0;
}
//将一个线性表置为空表
public void clear(){
this.N=0;
}
//判断当前线性表是否为空表
public boolean isEmpty(){
return N==0;
}
//获取线性表的长度
public int length(){
return N;
}
//获取指定位置的元素
public T get(int i){
return eles[i];
}
//向线型表中添加元素t
public void insert(T t){
if (N==eles.length){
resize(2*eles.length);
}
eles[N++]=t;
}
//在i元素处插入元素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];
}
//再把t元素放到i索引处即可
eles[i]=t;
//元素个数+1
N++;
}
//删除指定位置i处的元素,并返回该元素
public T remove(int i){
//记录索引i处的值
T current = eles[i];
//索引i后面元素依次向前移动一位即可
for(int index=i;index<N-1;index++){
eles[index]=eles[index+1];
}
//元素个数-1
N--;
if (N<eles.length/4){
resize(eles.length/2);
}
return current;
}
//查找t元素第一次出现的位置
public int indexOf(T t){
for(int i=0;i<N;i++){
if (eles[i].equals(t)){
return i;
}
}
return -1;
}
//根据参数newSize,重置eles的大小
public void resize(int newSize){
//定义一个临时数组,指向原数组
T[] temp=eles;
//创建新数组
eles=(T[])new Object[newSize];
//把原数组的数据拷贝到新数组即可
for(int i=0;i<N;i++){
eles[i]=temp[i];
}
}
@Override
public Iterator<T> iterator() {
return new SIterator();
}
private 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++];
}
}
}
//测试环节
public class SequenceListTest {
public static void main(String[] args) {
//创建顺序表对象
SequenceList<String> sl = new SequenceList<>(10);
//测试插入
sl.insert("王仙芝");
sl.insert("剑九黄");
sl.insert("李淳罡");
sl.insert(1,"徐凤年");
for (String s : sl) {
System.out.println(s);
}
//测试获取
String getResult = sl.get(1);
System.out.println("获取索引1处的结果为:"+getResult);
//测试删除
String removeResult = sl.remove(0);
System.out.println("删除的元素是:"+removeResult);
//测试清空
sl.clear();
System.out.println("清空后的线性表中的元素个数为:"+sl.length());
}
}
链表
概述:
- 链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只管的表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
- 链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成。
结点API设计
类名 | Node |
---|---|
构造方法 | Node(T t,Node next): 创建Node对象 |
成员变量 | T item:存储数据 Node next:指向下一个结点 |
实现结点类
public class Node<T> {
//存储元素
public T item;
//指向下一个结点
public Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
生成链表:
public static void main(String[] args) throws Exception {
//构建结点
Node<Integer> first = new Node<Integer>(11, null);
Node<Integer> second = new Node<Integer>(13, null);
Node<Integer> third = new Node<Integer>(12, null);
Node<Integer> fourth = new Node<Integer>(8, null);
Node<Integer> fifth = new Node<Integer>(9, null);
//生成链表
first.next = second; s
econd.next = third;
third.next = fourth;
fourth.next = fifth;
}
单向链表
概述:
- 单向链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据,指针域用来指向其后继结点。
- 链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点。
单向链表LinkList类:
- 构造方法:LinkList():创建LinkList对象
- 成员方法:
1.public void clear():空置线性表
2.publicboolean isEmpty():判断线性表是否为空,是返回true,否返回false
3.public int length():获取线性表中元素的个数
4.public T get(int i):读取并返回线性表中的第i个元素的值
5.public void insert(T t):往线性表中添加一个元素;
6.public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素。
7.public T remove(int i):删除并返回线性表中第i个数据元素。
8.public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则
返回-1。 - 成员内部类:private class Node:结点类
- 成员变量:
1.private Node head:记录首结点
2.private int N:记录链表的长度
单向链表的代码演示
import java.util.Iterator;
public class LinkList<T> implements Iterable<T>{
//记录头结点
private Node head;
//记录链表的长度
private int N;
//结点类
private class Node {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public LinkList() {
//初始化头结点、
this.head = new Node(null,null);
//初始化元素个数
this.N=0;
}
//清空链表
public void clear() {
head.next=null;
this.N=0;
}
//获取链表的长度
public int length() {
return N;
}
//判断链表是否为空
public boolean isEmpty() {
return N==0;
}
//获取指定位置i出的元素
public T get(int i) {
//通过循环,从头结点开始往后找,依次找i次,就可以找到对应的元素
Node n = head.next;
for(int index=0;index<i;index++){
n=n.next;
}
return n.item;
}
//向链表中添加元素t
public void insert(T t) {
//找到当前最后一个结点
Node n = head;
while(n.next!=null){
n=n.next;
}
//创建新结点,保存元素t
Node newNode = new Node(t, null);
//让当前最后一个结点指向新结点
n.next=newNode;
//元素的个数+1
N++;
}
//向指定位置i出,添加元素t
public void insert(int i, T t) {
//找到i位置前一个结点
Node pre = head;
for(int index=0;index<=i-1;index++){
pre=pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//创建新结点,并且新结点需要指向原来i位置的结点
Node newNode = new Node(t, curr);
//原来i位置的前一个节点指向新结点即可
pre.next=newNode;
//元素的个数+1
N++;
}
//删除指定位置i处的元素,并返回被删除的元素
public T remove(int i) {
//找到i位置的前一个节点
Node pre = head;
for(int index=0;index<=i-1;i++){
pre=pre.next;
}
//要找到i位置的结点
Node curr = pre.next;
//找到i位置的下一个结点
Node nextNode = curr.next;
//前一个结点指向下一个结点
pre.next=nextNode;
//元素个数-1
N--;
return curr.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.item.equals(t)){
return i;
}
}
return -1;
}
@Override
public Iterator<T> iterator() {
return new LIterator();
}
private 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.item;
}
}
//用来反转整个链表
public void reverse(){
//判断当前链表是否为空链表,如果是空链表,则结束运行,如果不是,则调用重载的reverse方法完成反转
if (isEmpty()){
return;
}
reverse(head.next);
}
//反转指定的结点curr,并把反转后的结点返回
public Node reverse(Node curr){
if (curr.next==null){
head.next=curr;
return curr;
}
//递归的反转当前结点curr的下一个结点;返回值就是链表反转后,当前结点的上一个结点
Node pre = reverse(curr.next);
//让返回的结点的下一个结点变为当前结点curr;
pre.next=curr;
//把当前结点的下一个结点变为null
curr.next=null;
return curr;
}
//测试代码
public static void main(String[] args) {
//创建顺序表对象
LinkList<String> sl = new LinkList<>();
//测试插入
sl.insert("王仙芝");
sl.insert("剑九黄");
sl.insert("李淳罡");
sl.insert(1,"徐凤年");
for (String s : sl) {
System.out.println(s);
}
System.out.println("------------------------------------------");
//测试获取
String getResult = sl.get(1);
System.out.println("获取索引1处的结果为:"+getResult);
//测试删除
String removeResult = sl.remove(0);
System.out.println("删除的元素是:"+removeResult);
//测试清空
sl.clear();
System.out.println("清空后的线性表中的元素个数为:"+sl.length());
}
}
双向链表
概述:
- 双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。
- 链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点。
双向链表TowWayLinkList类 - 构造方法:TowWayLinkList():创建TowWayLinkList对象
- 成员方法:
1.public void clear():空置线性表
2.publicboolean isEmpty():判断线性表是否为空,是返回true,否返回false
3.public int length():获取线性表中元素的个数
4.public T get(int i):读取并返回线性表中的第i个元素的值
5.public void insert(T t):往线性表中添加一个元素;
6.public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素。
7.public T remove(int i):删除并返回线性表中第i个数据元素。
8.public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则
返回-1。
9.public T getFirst():获取第一个元素
10.public T getLast():获取最后一个元素 - 成员内部类:private class Node:结点类
- 成员变量:
1.private Node first:记录首结点
2.private Node last:记录尾结点
2.private int N:记录链表的长度
双向链表代码演示
import java.util.Iterator;
public class TowWayLinkList<T> implements Iterable<T> {
//首结点
private Node head;
//最后一个结点
private Node last;
//链表的长度
private int N;
//结点类
private class Node{
public Node(T item, Node pre, Node next) {
this.item = item;
this.pre = pre;
this.next = next;
}
//存储数据
public T item;
//指向上一个结点
public Node pre;
//指向下一个结点
public Node next;
}
public TowWayLinkList() {
//初始化头结点和尾结点
this.head = new Node(null,null,null);
this.last=null;
//初始化元素个数
this.N=0;
}
//清空链表
public void clear(){
this.head.next=null;
this.head.pre=null;
this.head.item=null;
this.last=null;
this.N=0;
}
//获取链表长度
public int length(){
return N;
}
//判断链表是否为空
public boolean isEmpty(){
return N==0;
}
//获取第一个元素
public T getFirst(){
if (isEmpty()){
return null;
}
return head.next.item;
}
//获取最后一个元素
public T getLast(){
if (isEmpty()){
return null;
}
return last.item;
}
//插入元素t
public void insert(T t){
if (isEmpty()){
//如果链表为空:
//创建新的结点
Node newNode = new Node(t,head, null);
//让新结点称为尾结点
last=newNode;
//让头结点指向尾结点
head.next=last;
}else {
//如果链表不为空
Node oldLast = last;
//创建新的结点
Node newNode = new Node(t, oldLast, null);
//让当前的尾结点指向新结点
oldLast.next=newNode;
//让新结点称为尾结点
last = newNode;
}
//元素个数+1
N++;
}
//向指定位置i处插入元素t
public void insert(int i,T t){
//找到i位置的前一个结点
Node pre = head;
for(int index=0;index<i;index++){
pre=pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//创建新结点
Node newNode = new Node(t, pre, curr);
//让i位置的前一个结点的下一个结点变为新结点
pre.next=newNode;
//让i位置的前一个结点变为新结点
curr.pre=newNode;
//元素个数+1
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 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;
}
//删除位置i处的元素,并返回该元素
public T remove(int i){
//找到i位置的前一个结点
Node pre = head;
for(int index=0;index<i;index++){
pre=pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//找到i位置的下一个结点
Node nextNode= curr.next;
//让i位置的前一个结点的下一个结点变为i位置的下一个结点
pre.next=nextNode;
//让i位置的下一个结点的上一个结点变为i位置的前一个结点
nextNode.pre=pre;
//元素的个数-1
N--;
return curr.item;
}
@Override
public Iterator<T> iterator() {
return new TIterator();
}
private 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.item;
}
}
}
//测试类
public class TowWayLinkListTest {
public static void main(String[] args) {
//创建双向链表对象
TowWayLinkList<String> sl = new TowWayLinkList<>();
//测试插入
sl.insert("姚明");
sl.insert("科比");
sl.insert("麦迪");
sl.insert(1,"詹姆斯");
for (String s : sl) {
System.out.println(s);
}
System.out.println("第一个元素是:"+sl.getFirst());
System.out.println("最后一个元素是:"+sl.getLast());
//测试获取
String getResult = sl.get(1);
System.out.println("获取索引1处的结果为:"+getResult);
//测试删除
String removeResult = sl.remove(0);
System.out.println("删除的元素是:"+removeResult);
//测试清空
sl.clear();
System.out.println("清空后的线性表中的元素个数为:"+sl.length());
}
总结
顺序表与链表的比较:
- 相比链表 ,顺序表的插入效率比较低,而定义数组大小也是固定的,有时候通过扩容的方法使耗时突增,如果查找的比较多,建议使用顺序表。
- 相比顺序表,链表的查询操作性能会比较低。因此,如果我们的程序中查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表。