逻辑结构中的线性结构,有多种实现,比如线性表、队列、栈,这篇文章主要讲的是线性表,包括如何用java代码实现简易的线性表。
何为线性表?
通俗地理解,就是将多个数据元素一个一个串起来,拼成一个线性表,用java来表示,可以用方法来对这个线性表进行各种操作。
java代码实现
先定义一个接口,包括相关的操作线性表的方法
/*
通过接口定义一组线性表中的操作
*/
public interface MyList {
int getSize(); //返回线性表中元素的个数
Object get(int i); //获取索引值为i的元素
boolean isEmpty(); //判断线性表是否为空
void insert(int i,Object e); //在线性表的i索引值添加元素e
boolean insertBefore(); //在线性表元素p前面插入元素e
boolean insertAfter(); //在线性表元素p后面插入元素e
boolean contains(Object e); //判断线性表中是否包含元素e
int indexOf(Object e); //返回线性表中元素e的索引值,有则返回,无则返回-1
Object remove(Object e); //删除线性表中第一个与e相同的元素,并返回该元素
Object remove(int i); //删除线性表中索引值为i的元素,并返回该元素
Object replace(int i,Object e); //使用元素e替换线性表中i位置的元素,并返回旧的元素
}
对于线性表,可以有两种存储方式,顺序存储和链式存储。
先讲顺序存储,顺序存储长这个样子
其实就是java中的数组,不过涉及到两个操作,一个是数据元素的插入,一个是数据元素的删除
数据的插入是这么一回事
数据的删除是这么一回事
具体的代码如下:
如何掌握呢?以下是我自己的方法
1)实现MyList接口,有IDE可直接生成方法重写
2)先定义数组和容量,定义size来保存元素个数(插入一个数据元素时就size++)
3)定义构造方法,一个是默认初始化容量的数组,一个是自行定义容量的数组
4)当方法参数中需要传入下标时,一般需要先进行越界判断;当插入数据时,先判断是否已满;当删除数据时,先判断数组是否为空。第四点比较重要,后面其他的数据结构代码中,同样适用
5)方法的注释都写的很清楚了,代码也都不难,很容易理解
public class MyArrayList implements MyList {
private Object[] elements; //定义数组保存数据元素
private static final int DEFAULT_CAPACITY = 16; //数组的默认初始化容量
private int size; //保存数据元素的个数
//构造方法
public MyArrayList(){
elements = new Object[DEFAULT_CAPACITY];
}
//可指定容量的构造方法
public MyArrayList(int initialCapacity){
elements = new Object[initialCapacity];
}
//返回元素的个数
@Override
public int getSize() {
return size;
}
@Override
public Object get(int i) {
//判断i是否越界
if( i < 0 || i >= size ){
throw new IndexOutOfBoundsException(i+"越界");
}
return elements[i];
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void insert(int i, Object e) {
//先判断索引值i是否越界
if( i < 0 || i > size ){
throw new IndexOutOfBoundsException(i+"越界");
}
//如果数组已满,需要对数组扩容
if( size >= elements.length ){
expandSpace(); //数组扩容
}
//从i开始,把元素依次后移
for( int j = size ; j > i ; j-- ){
elements[j] = elements[j-1];
}
// 把元素e存储到i位置
elements[i] = e;
//元素的个数+1
size++;
}
//数组扩容方法
public void expandSpace(){
//定义一个更大的数组,m默认是扩容两倍
Object[] newElements = new Object[elements.length*2];
// 把原来的数组内容复制到新的数组,
for( int i = 0 ; i < elements.length ; i++){
newElements[i] = elements[i];
}
// 让原来的数组名指向新的数组
elements = newElements;
}
//在指定的元素前插入一个元素
@Override
public boolean insertBefore(Object p,Object e) {
//确定元素p在线性表中的位置
int index = indexOf(p);
if( index < 0 ){
return false; //p元素不存在,插入不成功
}
//插入元素
insert(index,e);
return true;
}
//在指定的元素后插入一个元素
@Override
public boolean insertAfter(Object p,Object e) {
//确定元素p在线性表中的位置
int index = indexOf(p);
if( index < 0 ){
return false; //p元素不存在,插入不成功
}
//插入元素
insert(index+1,e);
return true;
}
@Override
public boolean contains(Object e) {
return indexOf(e) >= 0;
}
@Override
public int indexOf(Object e) {
//如果添加的为null,则判断数组中是否有元素为null,有则返回
if(e == null){
for (int i = 0; i < size; i++) {
if (elements[i] == null){
return i;
}
}
}else { //不为空才能进行equals
//遍历数组
for (int i = 0; i < size; i++) {
if(e.equals(elements[i])){
return i;
}
}
}
return -1;
}
@Override
public Object remove(Object e) {
//获得e在线性表中的索引值
int index = indexOf(e);
if( index < 0 ){
return null; //线性表中不存在元素e
}
return remove(index);
}
@Override
public Object remove(int i) {
//判断i是否越界
if( i < 0 || i >= size ){
throw new IndexOutOfBoundsException(i+"越界");
}
//把要删除的元素保存起来
Object old = elements[i];
//把i+1开始的元素依次前移
for( int j = i; j < size - 1; j++ ){
elements[j] = elements[j+1];
}
//把最后的元素置为null
elements[size-1] = null;
//修改元素的个数
size--;
return old;
}
@Override
public Object replace(int i, Object e) {
//判断索引值是否越界
if( i < 0 || i >= size ){
throw new IndexOutOfBoundsException(i+"越界");
}
//保存原来的值
Object old = elements[i];
//替换
elements[i] = e;
//把原来的元素值返回
return old;
}
@Override
public String toString() {
//遍历数组中的每个元素
//把线性表中每个元素连接起来
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < size; i++) {
sb.append(elements[i]);
if( i < size-1 ){
sb.append(",");
}
}
sb.append("]");
return sb.toString();
}
}
可以定义一个测试类,来测试我们所写代码
package 线性表;
public class MyArrayListTest {
public static void main(String[] args) {
MyArrayList list1 = new MyArrayList();
//1)判断为空
System.out.println(list1.isEmpty()); //true
System.out.println(list1.getSize()); //0
//2)添加元素
list1.insert(0,"aa");
list1.insert(1,"bb");
list1.insert(0,"cc");
System.out.println(list1.isEmpty()); //false
System.out.println(list1.getSize()); //3
//3)打印输出
System.out.println(list1);
//4)判断是否存在
System.out.println(list1.indexOf("cc")); //0
System.out.println(list1.indexOf("bb")); //2
System.out.println(list1.indexOf("dd")); //-1
System.out.println(list1.contains("aa")); //true
System.out.println(list1.contains("xx")); //false
//5)删除
list1.remove("dd");
System.out.println(list1); //[cc,aa,bb]
list1.remove("bb");
System.out.println(list1); //[cc,aa]
list1.remove(0);
System.out.println(list1); //[aa]
//6)替换
list1.insert(0,"xx");
list1.insert(0,"oo");
list1.insert(0,"yy");
System.out.println(list1); //[yy,oo,xx,aa]
list1.replace(0,"YY");
System.out.println(list1); //[YY,oo,xx,aa]
//7)返回指定索引的元素
System.out.println(list1.get(0)); //YY
System.out.println(list1.get(1)); //oo
// System.out.println(list1.get(33)); //java.lang.IndexOutOfBoundsException
//8)在指定元素的前面/后面插入另外的元素
list1.insertBefore("oo","JJ"); //[YY,JJ,oo,xx,aa]
System.out.println(list1);
list1.insertAfter("oo","jj"); //[YY,JJ,oo,jj,xx,aa]
System.out.println(list1);
list1.insertAfter("TT","BB"); //[YY,JJ,oo,jj,xx,aa]
System.out.println(list1);
}
}
顺序存储的特点:
1)查询易。从代码可看出,return elements[i] 可看出,不需要遍历数组,根据下标就可以直接返回,得到要查询的元素。
2)插入、删除难。从代码可看出,在索引 i 插入时,需要将 i 后面的所有元素都循环赋值实现后移;删除索引 i 的元素,需要将 i 后面的所有元素都循环赋值实现前移;当元素个数较多的时候,执行效率很低。
再讲链式存储,链式存储长这样子
这个叫做单向链表,双向链表后面讲。可以看到,计算机给我们的数据元素分配的空间是不连续的。该数据元素在链表中称为结点(Node,在java中用一个类来表示),data和next都是这个结点的数据项,很容易理解的,看看这段java代码你就懂了。
private class Node{
Object data; //这个是存储的数据
Node next; //这个引用用来指向下一个结点,这样才能将所有的结点连在一起,形成链表
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
}
你可能会好奇,为什么这个类使用private,因为这是作为一个内部类,放在链表这个类里的,看看下面的代码你就懂了
public class MySingleLink implements MyList {
private Node head;
private int size;
@Override
public int getSize() {
return size;
}
@Override
public Object get(int i) {
checkIndex(i);
Node pNode = getNode(i);
return pNode.data;
}
@Override
public boolean isEmpty() {
return size == 0;
}
//添加结点分多种情况
//一、头结点为null的时候,链表不存在,刚刚添加的结点是头结点
//二、insert(0,xxx);
// 生成一个新结点newNode,newNode.next=head(此时head为null),head=newNode
//三、insert(2,ooo);
// 在2这个位置插入一个元素,需要将新节点的地址给1的next;需要将原结点2的地址给新结点的next
@Override
public void insert(int i, Object e) {
//判断索引值是否越界
if( i < 0 || i > size ){
throw new IndexOutOfBoundsException(i+"越界");
}
//创建结点
Node newNode = new Node(e,null);
//头结点为null的情况
if( head == null ){
head = newNode;
}else {
//在0位置插入结点
if( i == 0){
newNode.next = head; //修改新结点的next域指向原来的头结点
head = newNode; //刚插入的结点就是新的头结点
}else {
//插入结点。先找到i-1这个结点
Node pNode = head;
for(int x = 1; x < i; x++){
pNode = pNode.next; //不断将next赋值给自身
}
//注意,先修改新结点的next指针域,再修改i-1结点的指针域
newNode.next = pNode.next;
pNode.next = newNode;
}
}
//元素个数+1
size++;
}
@Override
public boolean insertBefore(Object p, Object e) {
int index = indexOf(p);
if ( index < 0 ){
return false; //元素p不存在
}
insert(index,e);
return true;
}
@Override
public boolean insertAfter(Object p, Object e) {
int index = indexOf(p);
if ( index < 0 ){
return false; //元素p不存在
}
insert(index+1,e);
return true;
}
@Override
public boolean contains(Object e) {
return indexOf(e) >= 0;
}
//链表中没有索引值
@Override
public int indexOf(Object e) { //输入值,返回结点的索引值
int i = 0; //保存元素e的索引值
Node pNode = head;
while(pNode!=null){
if( e == null && pNode.data == null ){
return i;
}else if( e != null && e.equals(pNode.data) ){
return i;
}
i++;
pNode = pNode.next;
}
return -1;
}
@Override
public Object remove(Object e) {
int index = indexOf(e);
if( index < 0 ){
return null;
}
return remove(index);
}
//一、删除头结点
// head = head.next
//二、删除的不是头结点
// i-1的结点.next = i结点.next
@Override
public Object remove(int i) {
if( i < 0 || i >= size ){
throw new IndexOutOfBoundsException(i+"越界");
}
Node pNode = head;
//删除头结点
if( i == 0 ){
head = head.next;
size--;
return pNode.data; //返回删除头结点的数据
}
//删除的不是头结点
//找到i-1这个结点
for( int x = 1 ; x < i ; x++ ){
pNode = pNode.next;
}
Object old = pNode.next.data; //保存结点数据
pNode.next = pNode.next.next; //修改i-1结点的next,让其指向i+1结点
size--;
return old;
}
@Override
public Object replace(int i, Object e) {
checkIndex(i);
Node pNode = getNode(i);
Object old = pNode.data;
pNode.data = e;
return old;
}
private void checkIndex(int i){
if( i < 0 || i >= size ){
throw new IndexOutOfBoundsException(i+"越界");
}
}
private Node getNode(int i){
if( i < 0 || i >= size ){
return null;
}
if( i == 0 ){
return head;
}
Node pNode = head;
for (int x = 1; x <= i; x++) {
pNode = pNode.next;
}
return pNode;
}
public String toString(){
//把链表中各个数据域中的数据连接起来
StringBuilder sb = new StringBuilder();
sb.append("[");
Node pNode = head;
while( pNode != null ){
sb.append(pNode.data);
if( pNode.next != null ){
sb.append(",");
}
pNode = pNode.next;
}
sb.append("]");
return sb.toString();
}
//定义一个内部类表示单向链表中的结点
private class Node{
Object data; //保存数据
Node next; //下个结点的引用
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
}
}
继续定义一个测试类,来测试我们写的代码
package 线性表;
public class MySingleLinkTest {
public static void main(String[] args) {
MySingleLink link = new MySingleLink();
//1)判断
System.out.println(link.isEmpty());
System.out.println(link.getSize());
//2)插入元素
link.insert(0,"aa");
link.insert(0,"bb");
link.insert(0,"cc");
link.insert(0,"dd");
//3)直接打印输出
System.out.println(link); //[dd,cc,bb,aa]
//4)判断元素是否存在
System.out.println(link.indexOf("dd")); //0
System.out.println(link.indexOf("aa")); //3
System.out.println(link.indexOf("xx")); //-1
System.out.println(link.contains("cc")); //true
//5)删除结点
System.out.println(link.remove("xx")); //null
System.out.println(link.remove("bb")); //bb
System.out.println(link); //[dd,cc,aa]
System.out.println(link.remove(0));
System.out.println(link); //[cc,aa]
//6)返回元素,元素替换
System.out.println(link.get(0)); //cc
System.out.println(link.replace(0,"CC")); //cc
System.out.println(link); //[CC,aa]
//7)在指定元素的前面/后面插入元素
link.insertBefore("aa","bb");
link.insertAfter("bb","BB");
System.out.println(link); //[CC,bb,BB,aa]
}
}