详解数组,链表和ADT

数组

先由一个例子复习一下数组的操作:

class  HighArray
{
    private long[] a;
    private  int nElems;
    //-----------------------------------
    public HighArray(int max)   //构造函数
    {
        a=new long[max];
        nElems = 0;
    }
    //-----------------------------------
    public boolean find(long searchKey)   //查找元素
    {
        int j;
        for(j=0;j<nElems;j++)
            if(a[j]==searcheKey)
                break;   //退出for循环,记录了j的值
        if(j==nElems)
                return false;   //没找到
            else
                return true;   //找到了
    }
    //--------------------------------------
    public void insert(long value)   //插入元素
    {
        a[nElems]=value;
        nElems++;
    }
    //--------------------------------------
    public boolean delete(long value)   //删除元素
    {
        int j;
        for(j=0;j<nElems;j++)
            if(value=a[j])
                break;
        if(j=nElems)
             return false;   //不存在那个元素
        else
        {
            for(int k=j;k<nElesm;k++)
                a[k]=a[k+1];   //遍历,从查找的那个位置起,把数组中的元素向前挪动一位
            nElems--;   //总长度减一
            return true;
        }
    }   //end  delete()
    //----------------------------------------
    public void display()
    {
        for(int j=0;j<nElems;j++)
            System.out.println(a[j]+" ");
        System.out.rpintln("");
    }
}

接着在客户端始测试 数组的 增 删 查 操作

class  HighArrayApp
{
    public static void main(String[] args)
    {
        int maxSize=100;
        HighArray arr;
        arr= new HightArray(maxSize);

        arr.insert(57);
        arr.insert(27);
        arr.insert(17);
        arr.insert(67);
        arr.insert(87);
        arr.insert(33);
        arr.insert(22);
        arr.insert(11);
        arr.insert(66);
        arr.insert(66);

        int searchKey=35;
        if(arr.find(searchKey))
            System.out.println("Found"+searchKey);
        else
            System.out.println("Can‘t found"+searchKey);

        arr.delete(55);
        arr.delete(66);

        arr.display();

    }
}

上例演示了一个数组增删查的过程,它的特点是高度的抽象,操作“增删改”都被封装到 HightArray类中,并且暴露出接口(insert(),delete(),display())供用户使用,这里的用户就是HightArrayApp,它只需调用方法完成操。

接着复习一下数组的二分查找

二分查找适用于有序数组,关于排序我在上一节博文有写,那么今天在这里默认数组有序的情况学习一下二分查找把

class  OrdArray
{
    private long[] a;
    private  int nElems;
    //-----------------------------------
    public HighArray(int max)
    {
        a=new long[max];
        nElems = 0;
    }
    //-----------------------------------
    public int size()
    {
        return nElems
    }
    //-----------------------------------
    public int find(long searchKey)   //查找元素
    {
        int lowerBound=0;
        int upperBound=nElems-1;
        int curIn;

        while(true)
        {
            curIn=(lowerBound+upperBound)/2;
            if(a[curIn]==searchKey)
                return curIn;
            else if(lowerBound>upperBound)
                return nElems;
            else
            {
                if(a[curIn]<searchKey)
                    lowerBound=curIn+1;
                else
                    upperBound=curIn-1;
            }
        }
    }
    //--------------------------------------
    public void insert(long value)   //插入元素
    {
        int j;
        for(j=0;j<nElmes;j++)
            if(a[j]>value);
                break;
        for(int k=nElems;k>j;k--)
            a[k]=a[k-1];
        a[j]=value;
    nElems++;
    }
    //--------------------------------------
    public boolean delete(long value)   //删除元素
    {
        int j;
        for(j=0;j<nElems;j++)
            if(value=a[j])
                break;
        if(j=nElems)
             return false;   //不存在那个元素
        else
        {
            for(int k=j;k<nElesm;k++)
                a[k]=a[k+1];   //遍历,从查找的那个位置起,把数组中的元素向前挪动一位
            nElems--;   //总长度减一
            return true;
        }
    }   //end  delete()
    //----------------------------------------
    public void display()
    {
        for(int j=0;j<nElems;j++)
            System.out.println(a[j]+" ");
        System.out.rpintln("");
    }
}

接着在客户端测试 数组的 增 删 查 操作

class  HighArrayApp
{
    public static void main(String[] args)
    {
        int maxSize=100;
        HighArray arr;
        arr= new HightArray(maxSize);

        arr.insert(57);
        arr.insert(27);
        arr.insert(17);
        arr.insert(67);
        arr.insert(87);
        arr.insert(33);
        arr.insert(22);
        arr.insert(11);
        arr.insert(66);
        arr.insert(66);

        int searchKey=35;
        if(arr.find(searchKey)!=arr.size())
            System.out.println("Found"+searchKey);
        else
            System.out.println("Can‘t found"+searchKey);
        arr.display();

        arr.delete(55);
        arr.delete(66);

        arr.display();

    }
}

数组的问题

  1. 在一个无序数组中插入只需要O(1)的时间,查找却需要O(N)
  2. 在一个有序数组中查找只需要O(logN),但插入却需要O(N)
  3. 不论是有序还是无序,删除操作,都需要花费O(N)
  4. 一旦数组被创建,大小就被固定住,数组初始化的大小不容易控制

链表

既然数组作为数据存储结构有一定的缺陷,那么接下来将介绍一种新的数据存储结构:链表。

链结点

在链表中,每个数据项都obeisance包含在链结点Link中。一个链结点是某个类的对象,这个类就叫做Link,因为一个链表中有许多类似的链结点,所以需要使用不同于链表的类来表达链结点。

每个Link对象都包含对下一个链结点引用的字段,通常叫做next。

但是链表对象包含有对第一个链结点的引用。

关系而非位置

数组是根据下标号直接访问,在链表中,寻找一个特定元素的唯一方法就是沿着这个元素链从头开始一直向下寻找。从第一项开始,刀第二个,然后到第三个。


单链表

这个链表仅有的操作是:

  1. 在链表头插入一个数据线
  2. 在链表头删除一个数据线
  3. 遍历链表显示它的内容

下面我们来看一下代码描述:

Link类 是链结点的抽象描述

class Link
{

    public int iData;//假设Link中的数据是int和double,实际有可能是Object对象
    public double dData;
    public Link next;   //这里看上去很突兀,但实际是一个链结点的引用,不是"Link中包含了一个Link"
    //------------------------------
    public Link(int id,double dd)
    {
        iData=id;
        dData=dd;
    }
    //-------------------------------
    public void display()
    {
        System.out.print("{"+iData+","+dData+"}");
    }
}   //end class Link

LinkList是链表的抽象

class LinkList
{
    private Link first;   //头结点的引用
    //-------------------------------
    public LinkList()
    {
        first=null;
    }
    //-------------------------------
    public void insertFirst(int id,double dd)
    {
        //插入新结点的过程:
        //first已经指向了链表的第一个结点,插入新的结点:
        //1.创建的newLink结点的next等于first;
        //2.然后改变first的值,使得first指向新创建的链结点
        Link newLink =new Link(id,dd);
        newLink.next=first;
        first=newLink;
    }
    //-------------------------------
    public Link deletefirst()
    {
        //通过把first重新指向第二个链结点,删除和第一个链结点的链接,记住first是链表的属性,next是链结点的属性
        Link temp=first;
        first=first.next;   //如何删除:first-->old next
        return temp;   //返回删除的链结点Link
    }
    //---------------------------------
    public Lind find(int key)  //查找某个元素
    {
        Link current =first;
        while(current.iData!=key)
        {
            if(current.next==null)
                return null;
            else
                current =current.next;
        }
        return current;
    }
    //--------------------------------
    public Link delete(int key)   //删除某个元素
    {
        Link current =frist;
        Link previous=first;
        while(current.iData!=key)
        {
            if(current.next==null)
              return null;
             else
             {
                previous=current;
                current=current.next;
             }
        }
        if(current==first)
             first=first.next;
         else
             previous.next=current.next;
         return current;
    }
    //---------------------------------
    public void displayList()   //输出
    {
        System.out.print("List(first-->last): ");
        Link current=first;
        while(current!=null)
        {
            current.displayLink();
            current=current.next;
        }
        System.out.println(" ");
    }
}   //end class LinkList

LinkListApp是测试链表和链结点的客户端

class LinkListApp
{
    public static void  main(String[] args)
    {
        LinkList theList=new LinkList();//创建新的链表
        theList.insertFirst(22,2.33);
        theList.insertFirst(44,4.33);
        theList.insertFirst(55,3.33);
        theList.insertFirst(88,34.33);

        theList.displayList();

        Link f=theList.find(44);
        if(f!=null)
            System.out.println("Found Link with key "+f.iData);
        else
            Systemm.out.println("Can‘t find link");

        Link d=theList.delete(88);

        theList.displayList();

        while(!theList.isEmpety())
        {
            Link aLink=theList.deleteFirst();
            System.out.print("Deleted");
            aLink.displayLink();
            System.out.println(" ");
        } 

        theList.displayList();
    }   //end main()
}

双端链表

双端链表与单链表的唯一区别是,增加了对最后一个链结点的引用。

这样就允许在表尾插入一个链结点。

当然我们也可以遍历整个链表直到表尾再插入。显然双端链表这样在末尾插入链结点效率更高。

有序链表

有序链表优于有序数组的地方就是插入的速度,因为链表的插入是不需要移动元素的,链表也可以扩展内存,数组的内存是固定的。

有序链表的缺点就是实现起来稍微复杂。

双向链表

双向链表的优点 就是反向遍历非常简单。

为什么?

因为双向链表的每个链结点都有两个属性,分别指向前一个结点和指向后一个结点。

关于有序链表和双向链表的代码叙述有兴趣的可以自行查阅

链表的效率

在表头插入和删除速度很快,花费O(1);

查找,删除和在指定链结点后面插入都需要搜索表中一半以上的链结点,需要O(N)次比较,虽然数组执行这些操作演示O(N)次比较,但是链表仍然要快一些,因为插入删除链结点时,链表不需要移动,增加的效率是显著的,特别是复制实际远远大于比较实际的时候。

链表比数组的优越性还体现在:链表需要多少内存就可以用多少内存,并且可以扩展。

数组太大导致效率低下,数组太小,使用的时候有可能空指针;向量是可扩展的数组,它改变长度的方式是增量扩展,扩大一倍。内存效率上比链表低的多,


ADT 抽象数据类型

接下来讨论一个比链表更广泛的话题:抽象数据类型

简单来说,是一种考虑数据结构的方式:着重于它做了什么,而忽略它 是怎么做的。

栈和队列都是ADT的例子,数组和链表都可以实现它们。

用链表实现栈

栈的push()和pop()操作实际是通过数组操作完成的

arr[++top]=data;

data=arr[top- -];

而链表是类似于这样完成:

theList.insertFist(data);

data=theList.deleteFirst();

栈的使用者调用push()和pop()方法来插入和删除栈中的元素,它们不需要知道栈是用链表还是数组实现的

class Link
{
    public long dData;
    public Link next;
    //--------------------------
    public Link(long dd)
    {
        dData=dd;
    }
    //--------------------------
    public void displayLink(){
        System.out.print(dData+" ");
    }
}   //end class Link
class LinkList{
    private Link first;   //头结点的引用
    //--------------------------------------
    public LinkList()
    {
        first=null;
    }
    //--------------------------------------
    public boolean isEmpty()
    {
        return fisrt==null;
    }
    //-------------------------------------
    public void insertFirst(long dd)
    {
        Link newLink=new Link(dd);
        newLink.next=first;
        first=newLink;
    }
    //--------------------------------------
    pubic long deleteFirst()
    {
        Link temp=first;
        first=first.next;
        return temp.dData;
    }
    //-------------------------------------
    public voi displayList()
    {
        Link current =first;
        while(current!=null)
        {
            current.displayLink();
            current=current.next;
        }
        System.out.println("");
    }
}   //end class
class Stack
{
    private LinkList theList;
    //------------------------------
    public Stack()
    {
        theList=new LinkList();
    }
    //------------------------------
    public void push(long i)
    {
        theList.insertFirst(j);
    }
    //------------------------------
    public void pop()
    {
        return theList.deleteFirst();
    }
    //------------------------------
    public boolean isEmpty()
    {
        return theList.isEmpty();
    }
    //-------------------------------
    public void displayStack()
    {
        System.out.print("Stack(top-->bottom):");
        theList.displayList();
    }
}   //end class
class LinkStackApp
{

    public static void main(String[] args)
    {
        LinkStack theStack=new LinkStack();
        theStack.push(20);
        theStack.push(40);

        theStack.displayStack();

        theStack.push(60);
        theStack.push(80);

        theStack.displayStack();

        theStack.pop();
        theStack.pop();

        theStack.displayStack();
    }   //end  main()
}   //end class

观察:整个程序,LinkStackApp中的main方法只和LinkStack类有关,LinkStack类只和LinkList类有关。main()和LinkList类是不能通信的。

数据类型和抽象

抽象数据类型分为两步,首先看看“数据类型”再考虑“抽象”

数据类型可以表示内置的类型,比如int,double,也可以用类来创建自己的数据类型。

抽象这个词的意思是“不考虑细节的描述和实现”,抽象是事物的本质和重要特征。

当“抽象数据类型”用于描述栈和队列这样的数据额结构时,它的意义被进一步扩展了。它意味着类的用户不知道方法是怎样运作的,也不知道数据是如何存储的。

对于栈来说,用户只知道push()和pop()方法的存在是被用户使用的,但不需要知道内部的实现,以及内部数据是如何存储。

接口,在ADT中有一个经常被称为“接口”的规范,同城是类的公有方法,在栈中,push() 和pop()就形成了接口。

时间: 2024-10-11 11:05:31

详解数组,链表和ADT的相关文章

go语言之行--结构体(struct)详解、链表

一.struct简介 go语言中没有像类的概念,但是可以通过结构体struct实现oop(面向对象编程).struct的成员(也叫属性或字段)可以是任何类型,如普通类型.复合类型.函数.map.interface.struct等,所以我们可以理解为go语言中的“类”. 二.struct详解 struct定义 在定义struct成员时候区分大小写,若首字母大写则该成员为公有成员(对外可见),否则是私有成员(对外不可见). type struct_variable_type struct { mem

Scala详解---------数组、元组、映射

一.数组 1.定长数组 声明数组的两种形式: 声明指定长度的数组 val 数组名= new Array[类型](数组长度) 提供数组初始值的数组,无需new关键字 Scala声明数组时,需要带有Array类名,且使用 () 来指明长度或提供初始值序列. 在JVM中,Scala的Array以Java数组的方式实现.如arr在JVM中的类型对应java.lang.String[],charArr对应char[]. 2.变长数组 ArrayBuffer,全称scala.collection.mutab

Scala详解---------数组相关操作

Scala中提供了一种数据结构-数组,其中存储相同类型的元素的固定大小的连续集合.数组用于存储数据的集合,但它往往是更加有用认为数组作为相同类型的变量的集合. 取替声明单个变量,如number0, number1, ..., 和number99,声明一个数组变量,如号码和使用numbers[0],numbers[1],...,numbers[99]表示单个变量.本教程介绍了如何声明数组变量,创建数组和使用索引的过程变量数组.数组的第一个元素的索引是数字0和最后一个元素的索引为元素的总数减去1.

链表----在链表中添加元素详解--使用链表的虚拟头结点

在上一小节中关于在链表中头部添加元素与在其他位置添加元素在逻辑上有所差别,这是由于我们在给链表添加元素时需要找到待添加元素位置的前一个元素所在的位置,但对于链表头来说,没有前置节点,因此在逻辑上就特殊一些,操作方式也就有所差别,需单独处理.为了针对头结点的操作方式与其他方式一致:接下来我们就一步一步引入今天的主题--使用虚拟头结点. 首先来看看之前的节点结构--第一个是头结点 相应的逻辑代码,感兴趣的可以看看我上一篇相关介绍,点击传送地址 为了能把关于头结点的操作与其他操作统一起来,我们来分析一

STL list链表的用法详解(转)

本文以List容器为例子,介绍了STL的基本内容,从容器到迭代器,再到普通函数,而且例子丰富,通俗易懂.不失为STL的入门文章,新手不容错过! 0 前言 1 定义一个list 2 使用list的成员函数push_back和push_front插入一个元素到list中 3 list的成员函数empty() 4 用for循环来处理list中的元素 5 用STL的通用算法for_each来处理list中的元素 6 用STL的通用算法count_if()来统计list中的元素个数 7 使用count_i

bash数组和字符串处理、yum命令详解及编译安装

8月22号主要内容: 一.bash中的数组 二.bash中字符串处理 三.高级变量及配置用户环境 四.yum详解 五.编译安装 一.bash中的数组 1.数组的组成和申明 (1) 数组:存储多个元素的连续的内存空间,相当于多个变量的 集合. (2) 组成:数组名和索引 索引:编号从0开始,属于数值索引 注意:索引可支持使用自定义的格式,而不仅是数值格式 ,即为关联索引,bash4.0版本之后开始支持. bash的数组支持稀疏格式(索引不连续) (3) 申明数组: declare -a ARRAY

搭建Android开发环境附图详解+模拟器安装(JDK+Eclipse+SDK+ADT)

——搭建android开发环境的方式有多种,比如:JDK+Eclipse+SDK+ADT或者JDK+Eclipse+捆绑好的AndroidSDK或者Android Studio. Google 决定将重点建设 Android Studio编译工具.Google 在去年年底终止支持其 Eclipse包括插件ADT.(Android Studio会是一个趋势). 这篇将仅作为一个笔记吧,因为之前一直使用Word,记事本记录一些笔记之类.(大神勿喷,如有错误不当的地方,还望指出.谢谢!) 搭建开发环境

一维 + 二维树状数组 + 单点更新 + 区间更新 详解

树状数组详解: 假设一维数组为A[i](i=1,2,...n),则与它对应的树状数组C[i](i=1,2,...n)是这样定义的: C1 = A1 C2 = A1 + A2 C3 = A3 C4 = A1 + A2 + A3 + A4 C5 = A5 C6 = A5 + A6 ................. C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 ................ 如图可知: 为奇数的时候他是代表他本身,而为偶数的时候则是代表着自

后缀数组学习笔记【详解|图】

后缀数组学习笔记[详解] 老天,一个后缀数组不知道看了多少天,最后终于还是看懂了啊! 最关键的就是一会儿下标表示排名,一会用数值表示排名绕死人了. 我不知道手跑了多少次才明白过来.其实我也建议初学者手跑几遍,但是一定要注意数组的意义,否则就是无用功. 数组含义: s[ ]:输入的字符串,预处理的时候会在末尾加上一个0 sa[ ]:它的下标就是后缀排名 x[ ] = t[ ]:用来保存第一关键字排名,注意!它的数值是排名.初始时恰好是字符串的ASCII码.字典序嘛! y[ ] = t2[ ]:它的