您的位置:首页 > 其它

算法基础-基本数据类型

2016-11-01 23:11 239 查看

基础

抽象数据类型

许多基础数据类型都和对的集合有关,具体来说,数据类型的值就是一组对象的集合,所有操作都是关于 添加 删除 或是访问集合中的对象.在本文中我们将学习三种这样的数据类型,这也是后面学习更加更加复杂数据类型的基础.

1,栈

2,队列

3,背包

概述

在本章中,我们所学习的支持泛型和迭代的栈,队列 和背包的实现所提供的抽象是我们能够编写简洁的用例程序来操作对象的集合.深入理解这些抽象数据类型非常重要,这是我们要研究算法和数据结构的开始.

原因有三

第一:我们将以这些数据类型为基石构造后面其他更高级的数据结构;

第二:他们展示了数据结构和算法的关系及同时满足多个有可能相互冲突的性能目标是所面临的挑战;

第三:我们要学习的若干算法的实现重点就是需要其中的抽象数据类型能够支持对对象集合的强大操作.

接下来看看这些出翔数据类型的详细代码及实现思路

泛型

集合类抽象数据类型的一个关键特性是我们应该可以用他们存储任意类型的数据.一种特别的java机制能够做到这一点, 泛型.也叫参数化类型 .泛型对编程语言的影响非常深刻.许多语言并没有这样的机制(包括早期的java版本).在每个类名后的记号定义为一个类型参数,它是一个象征性的占位符,表示的是用例将会使用的某种具体的数据类型,比如可以将stack 理解为某种元素的栈.在实现Stack时我们并不知道Item的具体类型,在使用栈时,用例会提供一种具体的数据类型,将Item替换为任意引用数据类型.

定容栈

/**
* 定容栈
* @author DELL-PC
*
*/
public class FixedCapactityStack {

private String [] as;
int N;

public FixedCapactityStack(int cap){
as = new String[cap];
}
public void push(String item){
as[N++]=item;
}
public String pop(){
return as[--N];
}
public int size(){
return N;
}
public boolean isEmpty(){
return N == 0;
}

}


一种表示泛型定容栈的数据类型

/**
* 定容栈---->> 泛型
* 支持泛型的定容栈
*    Item 是一个类型参数,用于表示此处将会使用某种具体类型的象征性的占位符
* @author DELL-PC
*
*/
public class FixedCapactityStack2<Item> {

private Item[] as;
int N;

public FixedCapactityStack2(int cap){
as = (Item[])new Object[cap];
}
public void push(Item item){
as[N++]=item;
}
public Item pop(){
return as[--N];
}
public int size(){
return N;
}
public boolean isEmpty(){
return N == 0;
}

}


使用数组白鸥时栈的内容意味着用例必须预先估计栈的大小,在java中数组一旦创建,其大小是无法改变的.因此我们可以修改数组的实现动态调整数组的大小,使它既可以满足所有的元素,又不至于浪费过多的空间.

可动态调整数组大小的泛型栈(非定容)

/**
* 定容栈---->> 泛型 ---->>动态调整数组的大小
* 支持泛型的定容栈
*    Item 是一个类型参数,用于表示此处将会使用某种具体类型的象征性的占位符
* 动态调整数组的大小,使得它既可以保存所有的元素,又不至于浪费过多的空间,实际上
* 完成这些目标非常简单.首先实现一个方法将栈移动到另一个大小不同的数组中
* @author DELL-PC
*
*/
public class FixedCapactityStack3<Item> {

private Item[] as;
int N;

public FixedCapactityStack3(int cap){
as = (Item[])new Object[cap];
}
/**
* 入栈
* @param item
*/
public void push(Item item){
if(N==as.length){//如果当前栈的元素等于数组的大小
resize(2*as.length);
}
as[N++]=item;
}
/**
* 出栈
* @return
*/
public Item pop(){
Item item = as[--N];
as
=null;//避免对象游离 参见 P85
if(N==as.length/4){//如果当前栈的大小是数组大小的四分之一
resize(as.length/2);
}
return item;
}
public int size(){
return N;
}
public boolean isEmpty(){
return N == 0;
}
/**
* 此方法用來调整数组的大小
* 将大小为 N<=max的栈移动到另一个新的大小为max的数组中
* @param max
*/
private void resize(int max){
Item[] items = (Item [])new Object[max];
for(int i=0;i<N;i++){
items[i] = as[i];
}
as = items;

}

}


在这个实现中,栈永远不会一处,使用率页永远不会低于四分之一.

[补充] 对象游离

java垃圾回收机制策略是回收所有无法被访问的对象的内存.在我们对 pop() 的实现中,被弹出的元素的引用仍然在数组中,这个元素实际上已经是个孤儿了—它永远也不会再被访问了,但java垃圾回收器无法知道这一点,除非该应用被覆盖.

用例已经不再需要这个元素了,数组中的引用仍然可以让他继续存在.这种情况(保存一个不需要的对象的引用)成为 对象游离 .在这里避免对象那个游离很容易,将它的值设为 null 即可.

支持迭代的栈API实现

* 定容栈---->> 泛型 ---->>动态调整数组的大小
* 支持泛型的定容栈
*    Item 是一个类型参数,用于表示此处将会使用某种具体类型的象征性的占位符
* 动态调整数组的大小,使得它既可以保存所有的元素,又不至于浪费过多的空间,实际上
* 完成这些目标非常简单.首先实现一个方法将栈移动到另一个大小不同的数组中
* @author DELL-PC
*
*/
/**
* 迭代: 可迭代集合数据类型:
* 迭代器接口,在java中,我们使用接口机制来规定一个类所必须实现的方法
* 其中迭代器接口实现在栈类的一个嵌套类中
*
*
*
* @author DELL-PC
*
*/
public class 下压栈<Item> {

private Item[] as;
int N;

public 下压栈(int cap){
as = (Item[])new Object[cap];
}
/**
* 入栈
* @param item
*/
public void push(Item item){
if(N==as.length){//如果当前栈的元素等于数组的大小
resize(2*as.length);
}
as[N++]=item;
}
/**
* 出栈
* @return
*/
public Item pop(){
Item item = as[--N];
as
=null;//避免对象游离 参见 P85
if(N==as.length/4){//如果当前栈的大小是数组大小的四分之一
resize(as.length/2);
}
return item;
}
public int size(){
return N;
}
public boolean isEmpty(){
return N == 0;
}
/**
* 此方法用來调整数组的大小
* 将大小为 N<=max的栈移动到另一个新的大小为max的数组中
* @param max
*/
private void resize(int max){
Item[] items = (Item [])new Object[max];
for(int i=0;i<N;i++){
items[i] = as[i];
}
as = items;

}

public Iterator<Item> iterator(){
return new ReverseArrayIterator();
}
/**
* 这份泛型的可迭代的Stack API的实现是所有集合类抽象数据类型实现的模版.它将所有元素保存在数组中,
* @author DELL-PC
*
*/
public class ReverseArrayIterator implements Iterator<Item>{

int i=N;
@Override
public boolean hasNext() {
return i>0;
}

@Override
public Item next() {

//支持后进先出的栈
return as[--i];
}

}
}


本文开头已经提到过,集合数据类型的基本操作之一就是,能够使用java的 foreach 语法通过迭代遍历,并处理集合中的每个元素.这种方式的代码既简洁又清晰.且不依赖与集合数据类型的具体实现

public Iterator<Item> iterator(){
return new ReverseArrayIterator();
}


这段代码保证了类必然会实现迭代接口的方法 hasNext() , next() 供用例的foreach()语法使用

下面是一个测试用例 Dijkstra双栈运算数求值算法;

package Dijkstra双栈运算数求值算法;

import java.util.Scanner;
import java.util.Stack;

public class Evaluate {
public static void main(String[] args) {

Stack<String> ops = new Stack<String>();
Stack<Double> vals = new Stack<Double>();

Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
String s= sc.next();
if(s.equals("(")){}
else if(s.equals("+")){ ops.push(s);}
else if(s.equals("-")){ ops.push(s);}
else if(s.equals("*")) {ops.push(s);}
else if(s.equals("/")){ ops.push(s);}
else if(s.equals("sqrt")){ops.push(s);}
else if(s.equals(")")){
//如果字符串为右括号则弹出运算符和操作数,计算结果并压入数值栈
String op = ops.pop();
Double v = vals.pop();
if(op.equals("+")){v=vals.pop()+v;}
else if(op.equals("-")){v=vals.pop()-v;}
else if(op.equals("*")){v=vals.pop()*v;}
else if(op.equals("/")){v=vals.pop()/v;}
else if(op.equals("sqrt")){v= Math.sqrt(v);}
vals.push(v);
}
else{
vals.push(Double.parseDouble(s));
}
//( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
//          System.out.println("运算结果:");
//          System.out.println("运算结果:"+vals.pop());

}
System.out.println("运算结果:"+vals.pop());

}

}


注意控制台输入表达式以空格隔开.

链表

定义

定义 链表是一种递归的数据结构,它或者为空(null),或者是指向一个节点(node)的引用,该节点含有一个泛型的元素和一个指向另一条链表的引用.

在面向对象编程中,实现链表并不困难,我们首先用一个嵌套类来定义节点的抽象数据类型.

private class Node{
Item item;
Node node;
}


我们没有定义任何方法会在代码中直接饮用实例变量:如果first是一个指向 Node 对象的变量,我们可以使用 first.item和first.next访问它的实例变量,这种类型的类有时也称为记录.

链表的操作:

在表头插入结点

首先,假设你希望向一条链表插入一个新的结点.最容易做到就是链表的开头.例如,要在首节点为first的给定链表开头插入字符串 “hello” 我们现将first 保存在 oldfirst ,然后讲一个新节点赋予first,并将它的item域设为 “hello” ,next域设为oldfirst.

从表头删除结点

接下来,假设你希望删除一条链表的首结点.这个操作更简单:只需要将 first.next指向first即可.一般来说,你希望在赋值之前得到该元素的值,因为一旦改变了first的值,就再也无法访问它曾经指向的结点了,曾经的结点对象就变成了一个孤儿,java的内存管理系统最终会回收它所占用的内存.

在表尾插入结点

如何在连彪的尾部添加一个新的结点?要完成这个人,我们需要一个指向李娜表最后哟个结点的链接,因为该节点的链接必须被修改并指向一个含有新元素的新节点.

我们不能在链表代码中草率的决定维护一个额外的链接,因为内个修改链表的操作都需要添加检查是否要修改该变量的代码.例如我们刚刚提到过的修改链表首节点的代码就可能改变指向连彪的尾结点的引用,因为当一个链表中只有一个结点时,它既是首节点也是尾结点.

其他位置的插入和删除操作

那么如果想要删除指定结点或者在指定结点插入一个节点该怎莫做呢,这就比前面几种操作更加复杂,这时,我们就必须使用双向链表.双向链表我们暂时不会用到,想了解更多链表的知识,可以去网上查询更多的链表操作.

下压堆栈(链表实现)API

package 链表;

import java.util.Iterator;

import 栈.下压栈.ReverseArrayIterator;

/**
* 链表实现的栈
* @author DELL-PC
*
*/
public class Stack_Node<Item> {

private Node first;
private int N;

private class Node{ Item item; Node node; }
public boolean isEmpty(){
return N==0;
}
public int size(){
return N;
}
public void push(Item item){
//向栈顶添加元素
Node oldFirst = first;
first = new Node();
first.item = item;
first.node= oldFirst;
N++;
}
public Item pop(){
//从栈顶删除元素
Item item = first.item;
first = first.node;
N--;
return item;
}
public Iterator<Item> iterator(){ return new ReverseArrayIterator(); }/**
* 这份泛型的可迭代的Stack API的实现是所有集合类抽象数据类型实现的模版.它将所有元素保存在数组中,
* @author DELL-PC
*
*/
private class ReverseArrayIterator implements Iterator<Item>{

int i=N;
@Override
public boolean hasNext() {
return i>0;
}

@Override
public Item next() {

//支持后进先出的栈
return pop();
}
}
}


使用链表的栈的好处:

1,所需的空间总是和集合的大小成正比,

2,操作所需的事件总是和集合的大小无关

队列

基于链表数据结构实现Queue API 也很简单.它记那个队列表示为一条从最早插入元素到最近插入元素的链表.实例变量 first指向队列的开头,实例变量 last 指向队列的结尾.

这样,要将那个元素元素入列(enqueue()),我们就将它添加到表尾(但在链表为空时,需要将 first 和 last 都指向新结点 );要 讲一个元素出列(dequeue()),我们就删除表头结点(代码和Stack的pop()相同,只是当链表为空时需要更新last的值)

先进先出的队列API

package 先进先出的队列;

public class Queue_Node<Item> {

private Node first;//指向最早添加的节点的链接
private Node last;//指向最近添加的节点的链接
int N=0;

private class Node{
Node next;
Item item;
}

public boolean isEmpty(){
return N==0;
}
public int size(){
return N;
}
/**
* 入队列操作:向表尾添加元素
* @param item
*/
public void enqueue(Item item){
Node oldLast = last;
last = new Node();
last.item= item;
last.next= null;

if(isEmpty()){
first = last;

}else{

oldLast.next = last;
}
N++;
}
/**
* 出队操作:从表头删除元素
* @return
*/
public Item dequeue(){
Item item = first.item;
first = first.next;
if (isEmpty()) last = null;
N--;
return item;
}
public Iterator<Item> iterator(){ return new ReverseArrayIterator(); }/**
* 这份泛型的可迭代的Stack API的实现是所有集合类抽象数据类型实现的模版.它将所有元素保存在数组中,
* @author DELL-PC
*
*/
private class MyIterator implements Iterator<Item>{

int i=N;
@Override
public boolean hasNext() {
return i>0;
}

@Override
public Item next() {

return dequeue();
}
}

}


在结构化寻出数据时,链表时数组的一种重要的替代方式.事实上,编程语言历史上的一块里程碑就是 McCathy 在20世纪 50 年代发明的LISP语言,而链表则是这种语言组织程序和数据的主要结构.

背包

背包是一种不支持从中删除元素的集合数据类型–它的目的就是帮助用例收集元素并迭代遍历所有收集到的元素

背包的API实现

package 背包;

import java.util.Iterator;

public class Bag<Item> {

private Bag<Item>.Node first;//链表的首节点
int N;

private class Node {
private Node nextNode;
private Item item;
}

public void add(Item item) {

Node oldFirst = first;
first = new Node();
first.item = item;
first.nextNode = oldFirst;
N++;
}

public int size() {
return N;
}
/**
* 这段代码保证了类必然会实现迭代接口的方法 hasNext() , next()
* 供用例的foreach()语法使用
* @return
*/
public Iterator iterator(){
return new ListIterator();
}

private class ListIterator implements Iterator<Item> {

@Override
public boolean hasNext() {
return first != null;
}

@Override
public Item next() {
Item item = first.item;
first = first.nextNode;

return item;
}

}

}


综述:

我们现在拥有两种表示对象那个集合的方式: 即 数组 和 链表 ,java内置了数组,链表也很容易使用java的标准方法实现.两者都非常基础,常常成为顺序存储和链式存储.

在后面的文章,我们会在各种抽象数据类型的视线中记那个多种方式归结并扩展这些基本的数据结构.其中一种重要的扩展就是各种含有多个链表的数据结构,例如二叉树,图等等.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 数据 队列 背包