数据结构系列教程------精讲(JAVA版)

 接 触不少程序员,都能够独立的作一下小型应用系统,和他们交谈起来才发现,他们纯粹是程序员,对基础的掌握太差,比喻java程序员,就是对jdk和各种框 架特别的熟悉,能够熟练的运用其中的各种包、类以及一些组件,确实能做出一下系统来,但是涉及到一些特殊的设计方法来就不行了,对基础掌握太差,包括现在 的很多培训,都是灌输这些所谓的实际应用的东西,学好基础才是最关键的东西,学一门语言很快,没有很好的基础、清晰的思路只能照葫芦画瓢了,为此,笔者结 合自己的学习经验写了系列教程,主要包括数据结构的全部内容:线性表、树、图、数组、集合、矩阵、排序、查找、哈希表,并将java的设计思想、方法及一 些常见的算法、设计模式贯穿其中,希望能给初学者一个很好的帮助,由于本人水平有限,同时请大家给与批评指正!
第一讲 线性表
    数据结构包括数据的逻辑结构、储存结构以及对数据的操作,线性表的含义就是:除了第一个和最后一个数据元素,其他的只有一个前驱元素和一个后继元素,如下图:
    A--->B--->C--->D
这个就是一个最简单的线性表的实例,结合定义可以很好的理解的,数据结构中最常见的就是提到抽象数据类型,所谓抽象数据类型就是在基本数据类型支持下用户 新设计的数据类型,通俗的说就是用户对基本数据类型(整型、浮点型等等)的组合应用成自己的一种新型的数据类型,也就是java中接口和抽象类,这么说可 能有些不妥,不过这样最好理解,前面讲到数据结构包括对数据的操作,这个设计数据结构的最终目的,下面我们就讲讲java数据结构中的线性表的设计思想。
由于线性表的数据集合可以表示为序列:A1,A2,A3……….An-1,每个元素的类型都是任意的,对于这个集合的操作可以归结如下:
(1)求出当前集合中数据元素个数(size);
(2)向当前数据集合任意位置插入新的数据(insert);
(3)删除当前数据集合中的任意数据(delete);
(4)获取当前数据集合中的任意数据(getData);
(5)判断当前数据集合是否为空;
,由于存在很多不同形式的线性表结构,对其操作方式当然也不一样,这样就要求设计一个大家都能使用的数据类型,由前面的讲述大家就可以想到必须要设计一个 抽象数据类型,也就是一个接口,这时可能有人问为什么不设计一个抽象类阿?这个问题留个大家思考,可以到论坛发表。Java中可以这样定义这个接口:

public interface List {
/**
* @author 天意
*/
        public void insert(int i,Object obj ) throws Exception;//在任意位置插入数据
        public Object delete(int i) throws Exception;//删除任意位置的数据
        public Object getData(int i) throws Exception;//获取任意位置的数据
        public int size();// 获取当前集合的大小
        public boolean isEmpty();//判断当前集合是否为空
}
,由于所有线性表的操作都是围绕以上而来的,所以上面的接口就可以通用于所有线性表了,这就告诉大家在设计程序时要做好充分的考虑,强调一个“广”字。
线性表包括顺序表和链式表,首先讲一下顺序表,顺序表顾名思义,就是数据元素按一定组合在一起的,逻辑上相邻的元素在物理储存上也是相邻的,如下如示例:
A0 A1 A2 A3 A4 A5 ……   

0        1           2          3         4          5                 maxSize-1
结合这个图我们可以想到:首先建立这个表就必须有个初始大小,然后才能对他就行实际操作,插入操作时可能会出现表已满、插入位置不存在的情况,删除时可能 出现表已空、删除的元素不存在的情况,获取时可能出现要求的元素不存在的情况,考虑这些因素就可以设计这个顺序表的操作类了SeqList.java,具 体内容如下:

public class SeqList implements List {

        /**
         * @author 天意
         */
        final int defaultSize=10;//默认为10个,你可以自己随便改
        int maxsize;
        int size;
        Object[] listArray;
        public SeqList(){
               initiate(defaultSize);
        }
        public SeqList(int size){
               initiate(size);
        }
        private void initiate(int sz) {
               //初始化
               maxsize=sz;
               size=0;
               listArray=new Object[sz];
        }
        public void insert(int i, Object obj) throws Exception {
               // 在i位置插入obj对象
      if(size==maxsize){
             throw new Exception("顺序表无法插入");
      }
      if (i<0||i>size){
             throw new Exception("参数错误");
      }
      for(int j=size;j>i;j--)
             listArray[j]=listArray[j-1];
          listArray[i]=obj;
          size++;
        }

        public Object delete(int i) throws Exception {
               // 删除i位置的元素
               if (size==0){
                      throw new Exception("顺序表已空无法删除!");
               }
               if(i<0||i>size-1){
                      throw new Exception("参数错误!");
               }
               Object it=listArray[i];
               for(int j=i;j<size-1;j++)
                      listArray[j]=listArray[j+1];
               size--;
               return it;
        }

        public Object getData(int i) throws Exception {
               // 获取i位置的元素并返回
               if(i<0||i>=size){
                      throw new Exception("参数错误!");
               }
               return listArray[i];
               }

        public int size() {
               //获得大小
               return size;
        }

        public boolean isEmpty() {
               // 判断是否为空
               return size==0;
        }
        public int MoreDataDelete(SeqList L,Object x)throws Exception{
               int i;
               int tag=0;
               for( i=0;i<L.size;i++){
                      if(x.equals(L.getData(i))){
                             L.delete(i);
                             i--;
                             tag=1;
                      }
                     
               }
               return tag;
        }

}
,以上就可以实现顺序表的所有操作了,你可以自己试一下,这里要说明的是,因为顺序表中储存的对象类型可以任意,所以设计时一定要使用Object类,当 然有时为了限定具体类型你可以重写这个类然后抛出相应的异常即可!

线性表的概念大家应该还记得,链式表是线性表的一个分类,当然也具备线性表的所有特性了,只不过它的结构方式特异而已,也就是和链子似的,和顺序表的不同 之处在于链式表引入对象应用,就是其他语言中的指针,每个链子(我自己的说法)包含一个数据元素(element)和一个指针域(next),这个链子就 称为节点,通俗的说有很多节点连接成的线性表就是链式表,根据其结构方式又可以分为单链表、单循环链表、双向链表,还有一种不常用的仿真链表,所有的链表 都有一个共同的特征,都是由节点组成,根据前一章的思想我们很自然的会想到要构造一个节点类Node.java:

public class Node {
/**
* @author 张钰
*/
        Object element;//数据元素
     Node next;
     Node(Node nextval){
             next=nextval;
     }
    Node(Object obj,Node nextval){
         element=obj;
         next=nextval;
    }
public Object getElement() {
        return element;
}
public void setElement(Object obj) {
        element = obj;
}
public Node getNext() {
        return next;
}
public void setNext(Node nextval) {
        next = nextval;
}
public String toString(){
        return element.toString();
}
    ,节点类的构造就是为了实现链表的操作,链表最常见的是单链表,单链表就是其每个节点都只有一个指向直接后继节点的指针,其中包括带头节点的和不带 头节点的,根据单链表的结构我们可以设计如下的类LinList.java(注意和java中的LinkList不一样,LinkList是个线性结构容 器,提供线性表、堆栈、队列等操作):
/**
* @author 张钰
*
*/
public class LinList implements List {

        Node head;//头指针
        Node current;//当前节点位置
        int size;//数据元素个数
        LinList(){
               head=current=new Node(null);
            size=0;
        }
        public void index(int i) throws Exception{ //定位元素
              if(i<-1||i>size-1){
                     throw new Exception("参数错误");
              }
              if(i==-1) return;
              current=head.next;
              int j=0;
              while((current!=null)&&j<i){
                     current=current.next;
                     j++;
              }
        }
        public void insert(int i, Object obj) throws Exception {
               // 插入
            if(i<0||i>size){
                   throw new Exception("参数错误");
            }
            index(i-1);
            current.setNext(new Node(obj,current.next));
            size++;
        }


        public Object delete(int i) throws Exception {
               // 删除
               if (size==0){
                      throw new Exception("链表已空无元素可删除!");
               }
               if(i<0||i>size-1){
                      throw new Exception("参数错误");
               }
               index(i-1);
               Object obj=current.next.getElement();
               current.setNext(current.next.next);
               size--;
               return obj;
        }

       
        public Object getData(int i) throws Exception {
               // 获取元素
               if(i<-1||i>size-1){
                      throw new Exception("参数错误");
               }
               index(i);
               return current.getElement();
        }

       
        public int size() {
               // 获取元素个数
               return size;
        }

        /* (非 Javadoc)
         * @see List#isEmpty()
         */
        public boolean isEmpty() {
               // 判断是否为空
               return size==0;
        }

}
,设计说明:
(1)构造函数完成三件事:创建头节点,使head和current均表示所创建的头节点,置s ize为0;
(2)定位成员函数index(int i)的实现,其设计方式是:用一个循环过程从头开始计数寻找第i个节点;
顺序表和单链表的比较:顺序表和单链表的逻辑功能完全一样,但是两者的应用背景以及不同情况下的使用效率略有不同,顺序表的主要优点就是支持随机读取,以 及内存空间利用效率高,顺序表的主要特点就是需要给出数组的最大数据元素个数,而这通常很难准确做到,另外,顺序表的插入和删除操作时需要移动较多的数据 元素,单链表的缺点就是每个节点中都有个指针,所以其空间利用率略低于顺序表,单链表不支持随机读取。
单链表另一种常见的形势就是循环单链表,其结构特点就是链表中最后一个节点的指针不再是结束标记,而是指向整个链表的第一个节点,从而使单链表形成一个 环,,就是在构造函数中增加head.next=head,在定位函数index(i)中,把循环结束条件current!=null换成 current!=head,这样最后一个节点就循环到第一个了!链表最常见的一个应用就是循环双链表,java中的LinkedList就是通过循环双 链表来实现的,其长度可以随着数据元素的增减很容易的变化,LinkedList提供了线性表、堆栈、队列、双端队列等数据结构所要求的全部成员函数,例 如addFirst()和removeFirst()就是支持堆栈所要求的成员函数,这里就不过多讲解了,回到循环双链表,双向链表中每个节点包括三个 域:element、next、prior,其中element是数据元素,next是指向后继节点的对象应用,prior是指向前驱节点的对象应用,

prior element next


设p为第i个节点,则p.next为第i+1个节点,p.next.prior==p,这就是双向链表的方式,结合前面的内容,双向链表类的设计留给大家,有兴趣的同学可以和我一起讨论!
最后还有仿真链表,链式储存结构中,实现元素之间的关系靠的是指针,但是也可以用数组来构造仿真链表,方法是在数祖中增加一个(或两个)int类型的变量域,这些变量用来表示后一个或前一个元素在数组中的下标,这就是仿真链表,其应用不是很多,这里就不多介绍。
第三讲 堆栈和队列

堆栈和队列都是特殊的线性表,他们的逻辑关系完全相同,差别是线性表的插入和删除操作不受限制,而堆栈只能在栈顶插入和删除,队列只能在队尾插入、队头删 除,堆栈和队列都可以分别用顺序储存结构和链式存储结构,堆栈的操作主要有入栈、出栈、取栈顶元素、是否为空,可以设计通用接口Stack..ava如 下:

/**
* @author
*
*/
public interface Stack {
     public void push(Object obj) throws Exception;//把数据元素obj插入堆栈
     public Object pop()throws Exception;//出栈,删除栈顶元素并返回
     public Object getTop()throws Exception;//获取栈顶元素
     public boolean notEmpty();//判断是否为空
}
下面就不同的结构展开:
(一)顺序堆栈
    具有顺序储存结构的堆栈称为顺序堆栈,顺序堆栈和顺序表的数据成员是相同的,只是顺序堆栈的入栈和出栈操作只能在当前栈顶进行,其结构可以参考下图:
栈底                                 栈顶
a0 a1 a2 a3 a4     

satck                                          top=5              maxStackSize-1

stack表示顺序堆栈储存数据的数组,a0、a1等表示已经入栈的数据,a0比a1先入栈,maxStackSize表示顺序堆栈数组stack的最大元素个数,top表示顺序堆栈的stack的当前栈顶的下标,设计顺序堆栈类如下SeqStack.java:


/**
* @author
*
*/
public class SeqStack implements Stack {

        /*
         * @see Stack#push(java.lang.Object)
         */
     final int defaultSize=10;
     int top;
     Object[] stack;
     int maxStackSize;
     public SeqStack(){
            initiate(defaultSize);
     }
     public SeqStack(int sz){
            initiate(sz);
     }
        private void initiate(int sz) {
               // 初始化
               maxStackSize=sz;
               top=0;
               stack=new Object[maxStackSize];
        }
        public void push(Object obj) throws Exception {
               // 插入堆栈
          if(top==maxStackSize){
                 throw new Exception("堆栈已满!");
          }
          stack[top]=obj;
          top++;
        }

        /*
         * @see Stack#pop()
         */
        public Object pop() throws Exception {
               // 出栈
               if(top==0){
                      throw new Exception("堆栈已空!");
               }
               top--;
               return stack[top];
        }

        /*
         * @see Stack#getTop()
         */
        public Object getTop() throws Exception {
               // 获取栈顶数据
               if(top==0){
                      throw new Exception("堆栈已空!");
               }
               return stack[top-1];
        }

        /*
         * @see Stack#notEmpty()
         */
        public boolean notEmpty() {
               // 判断是否为空
               return (top>0);
        }

}
(二) 链式堆栈
链式堆栈具有链式存储结构,用节点构造链,,每个节点由两个域组成,一个是存放数据元素的域element,另一个是存放下一个节点的对象引用域也就是指 针域next,堆栈有两端,插入数据和删除元素的一端为栈顶,另一端为栈底,链式堆栈都不带头节点(大家可以思考一下为什么?),链式堆栈类的设计 LinStack.java:
public class LinStack implements Stack{
Node head;//堆栈头
int size;//节点个数
public void LinStack(){
head=null;
size=0;
}
public void push(Object obj){//入栈
head=new Node(obj,head);//新节点为栈顶
}
public Object pop() throws Exception{ //出栈
if(size==0){
throw new Exception(“堆栈已空”);
}
Object obj=head.element;//取原栈顶元素
head=head.next;//原栈顶节点脱链
Size--;
Return obj;
}
public Boolean notEmpty(){
return head!=null; //是否空
}
public Object getTop(){
return head.element; //取栈顶元素
}
}
,堆栈由于其操作的特殊性而存在,在很多地方能起到独特的作用,比喻中缀表达式转换成后缀表达式,编译软件中的括号匹配问题(eclipse中就很好)都是使用堆栈的特殊性来设计算法,具体的问题大家可以和我一起交流!

下面讲讲队列,队列的特点就是从队尾插入、队头删除,也是一种特殊的线性表,队列的操作和堆栈一样主要有:入队列、出队列、取队头数据元素、是否为空,队列的接口类Queue.java可以设计如下:
/**
* @author
*
*/

public interface Queue{
public void append(Object obj) throws Exception;
public Object delete()throws Exception;
public Object getFront() throws Exception;
public Boolean notEmpty();
}
(三)顺序队列
具有顺序结构的队列就叫顺序队列,顺序队列存在假溢出问题,就是一个队列最大存储为6个元素,现在有a0 a1 a2 a3 a4 a5六个元素入队列 了,然后又有a0 a1 a2三个元素出队列了,这样剩下的三个元素占据的还是队列的后三个位置,这时候要是有新的元素入队列就会出现数组越界,而事实上 队列没有满,这就是假溢出问题,出现问题的原因就是队尾rear和队头front的值不能由所定义数组下界值自动转化成上界值而产生的,解决的办法就是把 顺序队列所使用的储存空间构造成一个逻辑上首尾相连的循环队列,当队尾rear和队头front达到maxSize-1后,再自加1自动到0,这样就不会 出现队列数组头部已空但队尾因数组下标越界而引起的溢出的假溢出问题,解决顺序循环队列的队列满和队列空状态判断问题通常三种方法:
   1.少用一个储存空间,以队尾rear加1等于队头front为队列满的判断条件,即此时队列满的判断条件为:(rear+1)%maxSize=front,队列空的条件为:rear=front;
    2.设置一个标志位,添加一个标志位,设标志位为tag,初始时置tag=0,每当入队列操作成功时就置tag=1,出队列操作成功时标志tag=0,那么此时队列满的判断条件是:
rear= =front && tag= =1,队列空的判断条件是:rear==front && tag= =0;
    3.设置一个计数器,添加一个计数器count,初始count=0,每当入队列操作成功时count加1,每当出队列操作成功count减1,这 样计数器不仅有标示功能还有计数功能,此时判断队列满的条件是:count>0 && rear= =front,队列空的条件 是:count= =0;
上面三种方法很明显最好的是第三种使用计数器的方法,采用这种计数器的办法可以设计顺序循环队列的类SeqQueue.java如下:
    public class SeqQueue implements Queue{
       static final int defaultSize=10;
       int front;
       int rear;
       int count;
       int maxSize;
       Object[] data;
       public SeqQueue(){
      initiate(defaultSize);
}
public SeqQueue(int sz){
initiate(sz);
}
public void initiate(int sz){
maxSize=sz;
front=rear=0;
count=0;
data=new Object[sz];
}
public void append(Object obj) throws Exception{
    if(count>0 && front= =rear){
throw new Exception(“队列已满!”);
}
data[rear]=obj;
rear=(rear+1)%maxSize;
count++
}
public Object delelte() throws Exception{
          if(count= =0){
     throw new Exception(“队列已空!”);
}
Object temp=data[front];
front=(front+1)%maxSize;
count- -
return temp;
}
public Object getFront() throws Exception{
     if(count= =0){
     throw new Exception(“队列已空”);
}
return data[front];
}
public Boolean notEmpty(){
    return count!=0;
}
}
以上就是顺序队列的java表示,大家可以自己做些例子测试一下,队列还有一种就是链式队列,链式队列就是具有链式结构的队列,链式队列通常设计成不带头 节点的结构,结合以前的链式表可以很容易设计出链式队列的类,这个任务就留给大家了,队列就是一种从队尾进入队头删除的特殊的顺序表,设计时还考虑了一种 优先级队列,就是给队列中的每个元素添加一个优先级标示,出队列时按优先级从高到低的顺序进行,这就是优先级队列,在系统进程管理中的应用很广泛。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值