最近从图书馆借了一本数据结构(Java 语言描述)的书,作者是丁海军老师。上过 C 语言版实现的课程,觉得两者思路大致一样,只是在表达方式有所不同,学完第一章线性表之后,决定写一篇博客为自己总结一下学习情况。
线性表(list)是一种线性结构。线性结构的特点是具有相同数据类型的 n (n >= 0) 个数据元素的有限序列,它们一个接着一个排列,第一个元素没有直接前驱,最后一个元素没有直接后继,中间的元素有一个直接前驱和一个直接后继。当线性表的长度为 0 时,成为空表,表示一个元素都没有。
数据结构的三要素,即数据的逻辑结构、数据的存储结构、数据操作。
数据的逻辑结构是对数据结构的数学抽象,它独立于计算机,是数据元素与数据元素之间存在的一种关系。数据的存储结构也叫物理存储结构,它是数据结构在计算机的实现。主要分为顺序存储结构和链式存储结构,线性表也从这两个结构来实现。数据操作,也就是我们可以在这个数据结构上进行的操作。如最基本的增、删、改、查。
下面我们使用 Java 先实现线性表的顺序存储结构,也叫顺序表。而链式存储结构实现的我们可以称为链表。既然线性表是抽象的,那么就我们可以抽象出来一些顺序表和链表共同的部分,这部分独立于计算机的实现,我们把它定义为一个抽象类 AbsList。下面是这个类的代码部分。
package oldbiwang.ds;
import java.util.Iterator;
public abstract class AbsList<T> implements Iterable<T> {
//长度
protected int length;
//返回第 i (i >= 0)个元素
abstract public T get(int i);
//设置第 i 个元素为 x
abstract public boolean set(int i, T x);
//查找,返回首次出现的关键字为 key 的元素
abstract public int indexOf(int begin, int end, T o);
//插入 x 作为第 i 个元素
abstract public void add(int i, T x);
//删除第 i 个位置的数据元素并返回删除对象
abstract public void remove(int i)
//返回一个迭代器
abstract public Iterator<T> iterator();
//判断线性表是否为空
public boolean isEmpty() {
return length == 0;
}
//返回线性表长度
public int length() {
return length;
}
//在线性表最后插入 x 元素
public void add(T x) {
add(length, x);
}
public void append(T x) {
add(length, x);
}
public int indexOf(T o) {
return indexOf(0, length, o)
}
public int indexOf(int begin, T o) {
return indexOf(begin, length, o);
}
public T remove(T o) {
return remove(indexOf(o));
}
}
我们实现顺序表 SeqList 将继承 AbsList 抽象类,它的结构大致如下:
线性表
下面是 SeqList.java 的代码
public class SeqList extends AbsList<T> implements Iterable<T> {
//顺序表的增量长度,用于扩容
private int incrementSize;
//保存顺序表数据的数组
protected T[] data;
//默认构造函数
public SeqList() {
//顺序表默认长度设置为 16,后期可自动增加
this(16);
}
//capacity 是顺序表的容量
@SuppressWarings("unchecked")
public SeqList(int capacity) {
if(capacity <= 0)
capacity = 16;
//顺序表的长度设置为 0
length = 0;
//增量因子为 0
incrementSize = 0;
//Java 没有泛型数组,用 Object[] 数组代替,分配 16 个数组元素
data = (T[])new Object[capacity];
}
//用数组 elem 初始化顺序表
public SeqList(T[] elem) {
length = elem.length;
incrementSize = 0;
data = elem;
}
public void setInc(int inc) {
//设置顺序表每次容量增加时增量大小,默认值为 16
incrementSize = inc;
}
//将顺序表的分配空间重新设置为 newSize
public void setCapacity(int newSize) {
data = Arrays.copyOf(data, newSize);
}
//设置长度
public void setLength(int len) {
if(len > data.length)
grow();
else if(len < 0)
len = 0;
length = len;
}
//获取顺序表的长度,同 size() ,为了方便
public int size() {
return length;
}
//取得顺序表下标为 i 的元素,也就是 data[i]
@Override
public T get(int i) {
if(i < 0 || i > length - 1)
return null;
return data[i];
}
//实现两个元素的比较
private int compare(T a, T b) {
if(a instanceOf Comparable &&
b instanceOf Comparable);
return ((Comparable)a).compareTo((Comparable)b);
else
return (a.toString().compareTo(b.toString()));
}
//交换下标为 i 的元素值
public void swap(int i, int j) {
T temp = data[i];
data[i] = data[j];
data[j] = temp;
}
//查找值为 o 的数据元素的下标,使用顺序查找算法
@Override
public int indexOf(int begin, int end, T o) {
//判断 o 是否为空
if(o == null) {
for(int i = begin; i < end; i++) {
if(data[i].equals(o))
return i;
}
}
else {
for(int i = begin; i < end; i++) {
//元素可以自定义 equals 函数
if(data[i].equals(o))
return i;
}
}
return -1;
}
//取逻辑下标为 i 的元素,同 get(i)
public T valueOf(int i) {
return get(i);
}
//内部使用,自动增加顺序表的容量
private void grow() {
int newSize;
if(incrementSize == 0)
newSize = (int)(1.618 * length);
else
newSize = incrementSize + data.length;
data = Arrays.copyOf(data, newSize);
}
//在位置 i 插入数据元素 x
@Override
public void add(int i, T x) {
if(length == data.length)
grow();
if(i < 0)
i = 0;
if(i > length)
i = length;
//从最后一个元素开始到 i 都往后移一个位置
for(int j = length - 1; j >= i; j--)
data[j + 1] = data[j];
data[i] = x;
length++;
}
//以有序的方式向顺序表增加数据元素 x
public void addSort(T x) {
insertOrder(length, x);
length++;
}
//对顺序表排序
public void sort() {
/*
利用 insertOrder 函数,它有两个参数,end 为整型,表示下标为 i(包括i) 之前的元素被排序,x 类型为 T,表示下标为 i 的那个元素。先从顺序表第 1、2个元素开始排序,
然后将1、2个元素排好序后再与第三个元素比较之后排序,依次类推,直到最后一个元素,这样顺序表就是排好序的顺序表,书里说这种方法就插入排序。也就是说以先前排好的顺序再与后一位元素比较排序,直到最后一个元素,得到一个排好序的顺序表。
*/
Iterator<T> itr = iterator();
itr.next();
int i = 1;
for(; itr.hasNext();) {
insertOrder(i, itr.next());
i++;
}
}
//内部使用,以有序方式插入数据元素 x
protected void insertOrder(int end, T x) {
//length 是线性表的长度,data.length 代表的是给 data 数组分配空间的长度
if(length == data.length)
grow();
int k;
for(k = end - 1; k >= 0; k--) {
if(comapre(x, data[k]) < 0)
data[k + 1] = data[k];
else
break;
}
data[k + 1] = x;
}
//删除下标为 i 的元素
@Override
public T remove(int i) {
if(i < 0 || i > length - 1)
throw new IndexOutOfBoundsException("下标越界 i = " + i);
T olddata = (T)data[i];
for(int j = i; j < length - 1; j++)
data[j] = data[j + 1];
data[--length] = null;
return olddata;
}
//清除整个顺序表
@Override
public void clear() {
// let gc do its work
for(int i = 0; i < length; i++)
//让 Java 虚拟机的垃圾收集器自动处理
data[i] = null;
length = 0;
}
//顺序表的最大容量
public int getCapacity() {
return data.length;
}
@Override
public String toString() {
StringBuilder strb = new StringBuilder("(");
for(int i = 0; i < length - 1; i++)
strb = strb.append(data[i].toString() + ",");
strb = strb.append(data[length - 1].toString() + ")");
return new String(strb);
}
//将顺序表转换为 object 数组
public Object[] toArray() {
return Arrays.copyOf(this.data, this.length);
}
//将顺序表转换为类型为 E 的数组
public T[] toArray(T[] a) {
if(a.length < length)
//Make a new array of as runtime type,but my contents:
return (T[])Arrays.copyOf(this.data, this.length);
System.arraycopy(this.data, 0, a, 0, this.length);
if(a.length > this.length)
a[length] = null;
return a;
}
/////////////////////////////
public Iterator<T> iterator() {
return new MyIterator();
}
//内部类,迭代器
class MyIterator implements Iterator<T> {
private int index = 0;
public boolean hasNext() {
//只要在调用 next()后,index 自加,确保 index 不等于顺序表的长度
return index != length;
}
@Override
public T next() {
//使用索引来获取 SeqList 中下标为 index的项
return get(index++);
}
@Override
public void remove() {
//为实现这个方法
}
}
//MyIterator end
///////////////////////////////////
/*
* @param args
*/
public static void main(String[] args) {
SeqList<Integer> ms = new SeqList<Integer>();
for(int i = 0; i < 10; i++)
ms.add((int)(10 * (9 - i + 1)));
System.out.println(ms);
System.out.println(ms.get(2));
System.out.println(ms.getCapacity());
System.out.println(ms.length());
System.out.println("indexOf:" + ms.indexOf(60));
System.out.println("==========================");
for(integer x: ms) {
System.out.println(x + ",");
}
SeqList myseq = new SeqList(10);
myseq.add(1);
myseq.add("oldbiwang");
myseq.add("王锐鹏");
myseq.add(25.0);
System.out.println();
System.out.println(myseq);
System.out.println(myseq.get(0).getClass());
System.out.println(myseq.get(1).getClass());
System.out.println(myseq.get(2).getClass());
System.out.println(myseq.get(3).getClass());
//测试一个简单的 pojo 的类 Person
SeqList<Person> sp = new SeqList<Person>(10);
Person p1 = new Person();
p1.setName("1王锐鹏");
p1.setSex("男");
sp.add(p1);
Person p2 = new Person();
p2.setName("2张三");
p2.setSex("男");
sp.add(p2);
Person p3 = new Person();
p3.setName("3貂蝉");
p3.setSex("女");
sp.add(p3);
Person p4 = new Person();
p4.setName("4阿尔法");
p4.setSex("男");
sp.add(p4);
Person p5 = new Person();
p5.setName("5阿尔法");
p5.setSex("男");
//重写 equals 方法,name 和 sex 都相等才是相等
System.out.println(sp.indexOf(p5));
/*System.out.println(p1 instanceof Comparable);
System.out.println(p2 instanceof Comparable);
System.out.println(p3 instanceof Comparable);*/
//注意要这样调用 sp.indexOf(p3); indexOf(p3);会报错
System.out.println(sp.indexOf(p3));
System.out.println("排序前:");
System.out.println(sp);
/* Iterator<Person> itr = sp.iterator();
System.out.print("( ");
for(;itr.hasNext();){
System.out.print("[" + itr.next() + "]" + " ");
}
System.out.println(" )"); */
//System.out.println(sp.get(0));
sp.sort();
Person p6 = new Person();
p6.setName("6欧比旺");
p6.setSex("男");
System.out.println("排序前:");
System.out.println(sp);
System.out.println(sp.length());
System.out.println("有序添加元素:");
sp.addSort(p6);
System.out.println(sp.length());
System.out.println(sp);
//在下标i 为 3的地方增加元素
sp.add(3, p5);
System.out.println(sp);
sp.sort();
System.out.println(sp);
//在表尾增加元素
sp.add(p5);
System.out.println(sp);
//删除第一个 p5 元素
sp.remove(p5);
System.out.println(sp);
sp.remove(p5);
System.out.println(sp);
//得到下标为 3 的元素
System.out.println("下标为 3 的元素" + sp.get(3));
//修改下标为 3 的元素
sp.set(3, p5);
System.out.println(sp);
Object[] array = sp.toArray();
for(int i = 0; i < array.length; i++) {
System.out.println((Person)array[i]);
}
Person[] a = new Person[15];
a = sp.toArray(a);
System.out.println(a);
for(int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
}
}
下面给出 Person.java 的代码
package oldbiwang.ds;
public class Person implements Comparable<Person> {
String name;
String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String toString() {
return "name: " + name + " sex: " + sex;
}
@Override
//实现 compareTo 方法
public int compareTo(Person o) {
if(name.compareTo(o.name) < 0)
return -1;
else
return 1;
}
/*
public boolean equals(Person o) {
System.out.println("equals in");
if(name.equals(o.name) && sex.equals(o.sex))
return true;
else
return false;
}*/
@Override
public boolean equals(Object o) {
//System.out.println("equals in");
if(name.equals(((Person)o).name) && sex.equals(((Person)o).sex))
return true;
else
return false;
}
}
至此顺序表的基本功能实现和测试基本完成,要附加功能的话也可以随时增加,比较复杂的是顺序的排序,领会插入排序的实现,还有一个就是迭代器的实现和使用,它可以屏蔽顺序表内部的实现细节,提供一个接口。
我们将实现链表 LinkList 将继承 AbsList 抽象类,它的结构大致如下:
线性表
我们将用一个类 Lnode 来表示链表的结点
Lnode.java 的实现代码如下:
package oldbiwang.ds;
//链表节点
public class Lnode<T> implements Comparable<Lnode<T>> {
//数据域
public T data;
//指针域,指向下一个元素
public Lnode<T> next;
public Lnode(T key) {
data = key;
next = null;
}
public Lnode(T key, Lnode<T> e) {
data = key;
next = e;
}
public boolean equals(Object e) {
Lnode<T> node = (Lnode<T>)e;
return data.equals(node.data);
}
//实现 Comparable 接口的 compareTo 方法
public int compareTo(Lnode<T> e) {
Comparable<T> x;
if(data instanceof Comparable) {
x = (Comparable)data;
return (int)x.compareTo(e.data);
}
else
throw new ClassCastException("类型无法比较!");
}
public String toString() {
return data.toString();
}
}
接下来是链表的 Java 实现,LinkList.java 如下:
package oldbiwang.ds;
import java.util.Iterator;
public class LinkList<T> extends AbsList<T> implements Iterable<T> {
//指向表头的头指针
Lnode<T> first;
//指向表尾的尾指针
Lnode<T> last;
//当前指针 写的时候忘了
Iterator<T> itr = null;
//默认构造函数
public LinkList() {
first = last = null;
length = 0;
//写的时候忘记了
itr = new LinkIterator();
}
//写的时候忘了
//比较两个结点的大小
private int compare(Lnode<T> a,Lnode<T> b) {
return a.compareTo(b);
}
//清除操作
public void clear() {
first = last = null;
length = 0;
}
//写的时候忘了
public int length() {
return length;
}
//写的时候忘了
public int size() {
return length;
}
//得到第 i 个元素,从 0 开始计数
public Lnode<T> getNode(int i) {
if(i < 0 || i > length - 1)
return null; //忘记判断
//忘记判断头结点
if(i == 0)
return first;
Lnode<T> p = first;
int j = 0;
while(p != null && j < i) {
p = p.next;
j++;
}
return p;
}
//得到第 i 个元素的值
public T get(int i) {
Lnode<T> node = getNode(i);
if(node != null)
return node.data;
else
return null;
}
//修改第 i 个元素的值,从 0 开始计数
public boolean set(int i, T x) {
Lnode<T> node = getNode(i);
if(node != null) {
node.data = x;
return true;
}
else
return false;
}
//增加结点操作
public void add(int i, T o) {
Lnode<T> p;
Lnode<T> node = new Lnode<T>(o, null);
int j = i - 1;
//忘记写 length == 0
if(first == null || length == 0) {
//在空链表中插入结点 node
first = node;
last = node;
}
else if(j < 0) {
//在头结点之前插入结点 node
node.next = first;
first = node;
}
else if(j >= length - 1) {
//在尾结点之后插入 结点node
last.next = node;
last = node;
}
else{
//在链表中间插入结点 s
p = getNode(j);
node.next = p.next;
p.next = node;
}
length++;
}
/*//我写的增加结点操作,有一些错误与冗余
public void add(int i, T o) {
Lnode<T> node = new Lnode<T>(o, null);
int j = i - 1;
if(first == null) {
first = node;
last = node;
length++;
return;
}
if(i <= 0) {
node.next = first;
first = node;
length++;
return;
}
if(i > 0 && i < length) {
Lnode<T> p = getNode(j);
node.next = p.next;
p.next = node;
length++;
return;
}
if(i >= length) {
last.next = node;
last = node;
length++;
return;
}
}*/
//下面三个 add 方法写的时候都忘记写了
public void add(T key) {
add(length, key);
}
public void addBack(T key) {
add(length, key);
}
public void addFront(T key) {
add(0, key);
}
//这个写的时候没思路
public void sort() {
LinkList<T> sl = new LinkList<T>();
Lnode<T> p;
p = this.removeNode(0);
while(p != null) {
sl.insertOrder(p);
p = this.removeNode(0);
}
this.first = sl.first;
this.last = sl.last;
this.length = sl.length;
}
//这个方法我已经忘记
public void addSort(T e) //throws NoComparatorOfType
{
Lnode<T> s = new Lnode<T>(e, null);
insertOrder(s);
}
private void insertOrder(Lnode<T> s) //throws NoComparatorOfType
{
Lnode<T> p1,p2;
length = length + 1;
if(first == null) {
//空链表
first = s;
last = first;
return;
}
if(compare(s, first) < 0) {
s.next = first;
first = s;
return;
}
if(compare(s,last) >= 0) {
last.next = s;
last = s;
return;
}
//被插结点 p 在 p1 和 p2 之前
p2 = first;
p1 = p2;
while(p2 != null) {
if(compare(s, p2) > 0) {
p1 = p2;
p2 = p2.next;
}
else
break;
}
s.next = p2;
p1.next = s;
return;
}
///////////////////////////////////
public void removeAll() {
clear();
}
//写的时候忘记了
public T remove(int i) {
Lnode<T> p = removeNode(i);
if(p != null)
return p.data;
else
return null;
}
//删除结点第 i 号结点,从 0 开始计数
public Lnode<T> removeNode(int i) {;
Lnode<T> p,q;
if(first == null)
return null;
if(i == 0) {
p = first;
first = first.next;
length = length - 1;
return p;
}
if(i >= 1 && i <= length - 1) {
p = getNode(i - 1);
q = p.next;
p.next = q.next;
if(q == last) {
last = p;
}
return q;
}
return null;
}
//下面三个方法写的时候忘记了
public T removeFront() {
return removeNode(0).data;
}
public T removeBack() {
return removeNode(length - 1).data;
}
public T remove() {
return removeNode(0).data;
}
public int indexOf(int begin, int end, T key) {
Lnode<T> p = getNode(begin);
int i = begin;
while(p != null && i < end) {
if(p.data.equals(key))
return i;
p = p.next;
i++;
}
return -1;
}
//写的时候忘记了
public T search(T key) {
Lnode<T> p = getNode(0);
while(p != null) {
if(p.data.equals(key))
return p.data;
p = p.next;
}
return null;
}
//写的时候忘记了
public boolean contains(T key) {
if(indexOf(key) == -1)
return false;
else
return true;
}
public String toString() {
Lnode<T> p = first;
if(p == null)
return "( )";
String str = "(";
while(p != null)
{
if(p == last) {
str = str + p.data.toString() + ")";
break;
}
else
str = str + p.data.toString() + "->";
p = p.next;
}
return str;
}
public Object[] toArray() {
Object[] a = new Object[length];
Lnode<T> p = first;
for(int i=0;i<length;i++) {
a[i] = p.data;
p = p.next;
}
return a;
}
@SuppressWarnings("unchecked")
public <E> E[] toArray(E[] a) {
if(a.length < length)
a = (E[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(),length);
int i = 0;
Object[] result = a;
Lnode<T> x = this.first;
for(i = 0; i < length; i++) {
result[i] = x.data;
x = x.next;
}
if(a.length < length)
a[length] = null;
return a;
}
public Iterator<T> iterator() {
this.itr = new LinkIterator();
return this.itr;
}
//内部类,迭代器
private class LinkIterator implements Iterator<T> {
private int index = 0;
private Lnode<T> current = first;
public boolean hasNext() {
//只要在调用next()后,index自加,确保
return (index != length() &&
current != null);
}
@Override
public T next() {
T temp = current.data;
current = current.next;
index++;
return temp;
}
public int nextIndex() {
return index++;
}
@Override
public void remove() {
//未实现这个方法
}
}
public static void main(String[] args) {
LinkList<Person> linkList = new LinkList<Person>();
Person p1 = new Person();
p1.setName("Obiwan");
p1.setSex("male");
linkList.add(p1);
Person p2 = new Person();
p2.setName("Yoda");
p2.setSex("male");
linkList.add(p2);
Person p3 = new Person();
p3.setName("Vader");
p3.setSex("male");
linkList.add(p3);
Person p4 = new Person();
p4.setName("Lisa");
p4.setSex("female");
linkList.add(p4);
System.out.println(linkList);
//排序
System.out.println("排序后");
linkList.sort();
System.out.println(linkList);
//方法一:迭代器使用 foreach 输出
System.out.println("foreach 输出:");
for(Person p : linkList)
System.out.println(p);
//方法二:迭代器使用 while 和 iterator 循环输出
System.out.println("iterator 输出:");
Iterator<Person> itr = linkList.iterator();
while(itr.hasNext()) {
System.out.println(itr.next() + " ");
}
System.out.println("\n===============================");
System.out.println("length = " + linkList.length());
System.out.println("size = " + linkList.size());
Person p5 = new Person();
p5.setName("Obiwan");
p5.setSex("male");
System.out.println("contains p5: " + linkList.contains(p5));
System.out.println("p5 index of: " + linkList.indexOf(p5));
System.out.println("search p5: " + linkList.search(p5));
Person p6 = new Person();
p6.setName("Oldbiwang");
p6.setSex("male");
linkList.addSort(p6);
System.out.println("addSort: " + linkList);
System.out.println("===============================\n");
System.out.println("===============================");
Person p7 = new Person();
p7.setName("Luke");
p7.setSex("male");
System.out.println("before addFront: " + linkList);
linkList.addFront(p7);
System.out.println("after addFront: " + linkList);
linkList.addBack(p7);
System.out.println("after addBack: " + linkList);
Person pGet = linkList.get(3);
System.out.println("pGet: " + pGet);
System.out.println("after Get:" + linkList);
linkList.set(linkList.length() - 1,p1);
System.out.println("after set:" + linkList);
linkList.remove(linkList.size() - 1);
System.out.println("after remove: " + linkList);
System.out.println("再次排序");
linkList.sort();
System.out.println(linkList);
System.out.println("===============================\n");
System.out.println("toObjectArray");
Object[] o = linkList.toArray();
for(Object person : o) {
System.out.println("person: " + (Person)person);
}
System.out.println("to E Array");
//Person[] e = new Person[4];输出都为空.所以数组长度要大于链表长度
Person[] e = new Person[10];
linkList.toArray(e);
for(int i = 0; i < e.length; i++) {
System.out.println("person: " + e[i]);
}
}
}
链表的基本实现和测试也基本完成,使用的也是 pojo Person 类,链表的排序比较简单,新创建一个链表,将没排序链表的值插入进去,最后得到一个排好序的链表再返回来。链表是链式存储,也就是在内存里的位置地址不是连续的,而是分散的。
总结:顺序表和链表都是线性表的实现,它们各有优点,各有缺点。如果是经常改变的结构,如插入、删除等操作比较频繁的,使用链表好一点。如果对于不经常改变,查询和修改元素值比较多的结构,就多使用顺序表。