1. 绪论 2. 线性表 线性表是一种最常用、最简单,也是一种最基本的数据结构,它是学习其他数据结构的基础。
线性表 在计算机中可以用$\begin{cases}顺序存储 \\ 链式存储\end{cases}$两种存储结构来表示,其中,顺序存储的线性表成为顺序表,链式存储的线性表成为链表,链表 又分为:$\begin{cases} 单链表 \\ 双向链表 \\ 循环链表\end{cases}$。
特点:
2.1 顺序表 定义:
顺序表是用一组地址连续 的存储单元依次存放线性表中各个数据元素的存储结构。
特点:
在线性表中逻辑相邻的数据元素,在物理存储元素上也是相邻的
存储密度高,但需要预先分配“足够应用的存储空间,这可能将会造成存储空间的浪费,其中,$存储密度=\frac{数据元素本身值所需的存储空间}{该数据元素实际所占用的空间}$
便于随机存取
不便于插入和删除操作没这事因为在顺序表上进行插入和删除操作会引起大量数据元素的移动
顺序表的局限性 :
若要为顺序表扩充存储空间,则需要重新创建一个地址连续的更大存储空间,并把原有的数据元素都复制到新的存储空间中
因为顺序表存储要求逻辑上相邻的数据元素,在物理存储位置上也是相邻的,这就使得增删数据元素则会引起平均约一半的数据元素的移动
总结 ——查询快、增删慢!
顺序表代码实现: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 package cn.dataStructure.SqList;public class SqList { private Object[] listElem; private int curlen; public SqList (int maxSize) { curlen = 0 ; listElem = new Object[maxSize]; } public void clear () { curlen = 0 ; } public boolean isEmpty () { return curlen == 0 ; } public int length () { return curlen; } public Object get (int i) throws Exception { if (i<0 || i> curlen-1 ) throw new Exception("第" + i + "个元素不存在" ); return listElem[i]; } public void insert (int i,Object x) throws Exception { if (curlen == listElem.length) throw new Exception("顺序表已满" ); if (i<0 || i>curlen) throw new Exception("插入位置不合法" ); for (int j = curlen; j < i ; j--) listElem[j] = listElem[j-1 ]; listElem[i] = x; curlen++; } public void remove (int i) throws Exception { if (i<0 || i>curlen -1 ) throw new Exception("删除位置不合法" ); for (int j = i; j < curlen -1 ; j++) listElem[j] = listElem[j+1 ]; curlen--; } public int indexOf (Object x) { int j =0 ; while (j<curlen && !listElem[j].equals(x)) j++; if (j<curlen) return j; else return -1 ; } public void display () { for (int j = 0 ; j < curlen; j++) { System.out.println(listElem[j] + "" ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package cn.dataStructure.SqList;public class SqListTest { public static void main (String[] args) throws Exception { SqList L = new SqList(10 ); L.insert(0 ,"a" ); L.insert(1 ,"z" ); L.insert(2 ,"d" ); L.insert(3 ,"z" ); System.out.println("此顺序表为:" ); L.display(); int order = L.indexOf("z" ); if (order !=-1 ) System.out.println("顺序表中第一次出现的值为'z'的数据元素位置为:" +order); else System.out.println("此顺序表中不包含值为z的属于元素" ); } }
2.2 链表 定义:
顺序表适合表示静态线性表,一旦形成以后,就很少进行插入和删除操作,对于需要频繁插入和删除的动态线性表,通常采用链式存储结构 。
特点:
链式结构不要求逻辑上相邻的数据元素在物理上也相邻,它是用一组地址任意的存储单元来存放数据元素的值,故它没有顺序结构某些操作上的局限性,但却失去了随机存取的特点,在链式结构上只能进行顺序存取
单链表代码实现: Node类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package cn.dataStructure.LinkList;public class Node { public Object data; public Node next; public Node () { this (null ,null ); } public Node (Object data) { this .data = data; } public Node (Object data, Node next) { this .data = data; this .next = next; } }
LinkList类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 package cn.dataStructure.LinkList;import java.util.Scanner;public class LinkList { public Node head; public LinkList () { head = new Node(); } public LinkList (int n, boolean Order) throws Exception { this (); if (Order) create1(n); else create2(n); } public void create1 (int n) throws Exception { Scanner sc = new Scanner(System.in); for (int j = 0 ; j<n; j++) insert(length(),sc.next()); } public void create2 (int n) throws Exception { Scanner sc = new Scanner(System.in); for (int j = 0 ; j<n; j++) insert(0 ,sc.next()); } public void clear () { head.data = null ; head.next = null ; } public boolean isEmpty () { return head.next == null ; } public int length () { Node p = head; int length = 0 ; while (p != null ){ p = p.next; ++ length; } return length-1 ; } public Object get (int i) throws Exception { Node p = head; int j = -1 ; while (p!=null && j<i){ p = p.next; ++j; } if (i<0 || p == null ){ throw new Exception("get的第" + i + "个元素不存在" ); } return p.data; } public void insert (int i, Object x) throws Exception { Node p = head; int j = -1 ; while (p != null && j< i-1 ){ p = p.next; ++j; } if (j>i-1 || p == null ) throw new Exception("插入位置不合法hh" ); Node s = new Node(x); s.next = p.next; p.next = s; } public void remove (int i) throws Exception { Node p = head; int j = -1 ; while (p != null && j <i){ p = p.next; ++j; } if (i<0 || p ==null ){ throw new Exception("删除的第" + i + "个元素不存在" ); } } public int indexOf (Object x) throws Exception { Node p = head; int j = -1 ; while (p!=null && !p.data.equals(x)){ p = p.next; ++j; } if (p==null ){ throw new Exception("不存在值为" + x + "的结点" ); } else return j; } public void display () { Node node = head.next; while (node!=null ){ System.out.println(node.data + "" ); node = node.next; } System.out.println(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package cn.dataStructure.LinkList;public class LinkListTest { public static void main (String[] args) throws Exception { LinkList L = new LinkList(); L.display(); L.insert(0 ,0 ); L.insert(1 ,1 ); L.display(); System.out.println("链表长度为" + L.length()); LinkList L2 = new LinkList(3 ,false ); L2.display(); System.out.println("链表长度为" + L2.length()); LinkList L3 = new LinkList(3 ,true ); L3.display(); System.out.println("链表长度为" + L3.length()); } }
输出结果:
2.3 链表VS顺序表
与顺序表相比较,链表比灵活,它既不要求在预先分配的一块连续的存储空间中存储 线性表的所有数据元素,也不要求按其逻辑顺序来分配存储单元,可根据需要进行存储空间 的动态分配!因此,当线性表的长度变化较大或长度难以估计时,用链表。但在线性表的 长度基本可预计且变化较小的情况下,宜用顺序表,因为链表的存储密度较顺序表的低,且 顺序表具有随机存取的优势!
在顺序表中按序号访问第i个数据元素时的时间复杂度为O(1),而在链表中做同样操 作的时间复杂度为O(n)所以若要经常对线性表按序号访问数据元素时,顺序表要优先 链表;但在顺序表上做插入和删除操作时,需要平均移动一半的数据元素,而在链表上做插 入和删除操作,不需要移动任何数据元素,虽然也要查找插入或删除数据元素的位置,但由 于主要是比较操作,所以从这个角度考虑,链表要优先于顺序表
总之,链表比较灵活,** 插入和删除操作的效率较高,但链表的空间利用率较低,适合于实 现动态的线性表 ;顺序表实现比较简单,因为任何高级程序语言中都有数组类型,并且空间 利用率也较高,可高效地进行随机存取,但顺序表不易扩充,插入和删除操作的效率较低,适 合于实现相对“稳定”的静态线性表。两种存储结构各有所长,各种实现方法也不是一成不 变的。在实际应用时,必须以这些基本方法和思想为基础,抓住两者各自的特点并结合具体 情况,加以创造性地灵活应用和改造,用最合适的方法来解决问题。
3.栈与队列 定义:
栈和队列可被看成是两种操作受限 的特殊线性表,其特殊性体现在它们的插入和删除操作都是控制在线性表的一端或两端进行。
3.1 栈
栈是一种特殊的线性表,栈中的元素以及数据元素间的逻辑关系和线性表相同,区别在于:
线性表的插入和删除操作可以在表的任意位置进行,而栈只允许在表的尾端进行
特点:
先进后出(First In Last Out)
几个基本操作:
1 2 3 4 5 6 7 8 9 10 11 12 ```clear()```:置空 ```isEmpty()```:判空 ```length()```:返回栈中元素个数 ```peek()```:读取栈项元素并返回其值,若栈为空,则返回null ```push()```:入栈——将数据元素x压入栈顶 ```pop()```:出栈——删除并返回栈顶元素
3.1.1 顺序栈 类的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package cn.dataStructure.SqStack;public class SqStack { private Object[] stackElem; private int top; public SqStack (int maxSize) { top = 0 ; stackElem = new Object[maxSize]; } public void clear () { top = 0 ; } public boolean isEmpty () { return top == 0 ; } public int length () { return top; } public Object peek () { if (!isEmpty()) return stackElem[top-1 ]; else return null ; } public void push (Object x) throws Exception { if (top == stackElem.length) throw new Exception("栈已满" ); else stackElem[top++] = x; } public Object pop () { if (isEmpty()) return null ; else return stackElem[--top]; } public void display () { for (int i = top-1 ; i >=0 ; i--) { System.out.print(stackElem[i].toString() + " " ); } } }
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 package cn.dataStructure.SqStack;public class sqStackTest { public static void main (String[] args) throws Exception { SqStack sqStack = new SqStack(10 ); for (int i = 0 ; i < 10 ; i++) { sqStack.push(i); } sqStack.display(); } }
3.1.2 链栈 定义结点类,与之前一致:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package cn.dataStructure.LinkStack;public class Node { public Object data; public Node next; public Node () { this (null ,null ); } public Node (Object data) { this .data = data; } public Node (Object data, Node next) { this .data = data; this .next = next; } }
定义链栈类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package cn.dataStructure.LinkStack;public class LinkStack { private Node top; public void clear () { top = null ; } public boolean isEmpty () { return top == null ; } public int length () { Node p = top; int length = 0 ; while (p!=null ) { p = p.next; length++; } return length; } public Object peek () { if (!isEmpty()) return top.data; else return null ; } public void push (Object x) { Node p = new Node(x); p.next = top; top = p; } public Object pop () { if (isEmpty()) { return null ; } else { Node p = top; top = top.next; return p.data; } } public void display () { Node p = top; while (p!=null ) { System.out.print(p.data.toString() + " " ); p = p.next; } } }
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package cn.dataStructure.LinkStack;public class LinkStackTest { public static void main (String[] args) { LinkStack LS = new LinkStack(); for (int i = 0 ; i < 10 ; i++) { LS.push(i); } LS.display(); System.out.println("\n" + "被删除元素为:" + LS.pop()); LS.display(); } }
3.2 队列
定义:
队列是另一种特殊的线性表,它的特殊性体现在队列只允许在表尾插入数据元素,在表头删除数据元素,所以队列也是一种操作受限的特殊线性表
特点:
先进先出
几个基本操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ```isEmpty()```:置空 ```length()```:求取队列数据元素个数 ```peek()```:读取队首元素并返回其值。 ```offer()```:入队操作,将数据元素x插入到队列中使其成为新的队尾元素。 ```poll()```:出队操作,删除队首元素并返回其值,若队列为空,则返回null。 ```java public interface IQueue { public void clear(); public boolean isEmpty(); public int length(); public Object peek(); public void offer(Object x) throws Exception; public Object poll() }
3.2.1 顺序队列 与顺序栈类似,在顺序队列的存储结构中,需要分配一块地址连续的存储区域来一次存放队列中从队首到队尾的所有元素。这样也可以用一维数组来表示,假设数组名为queueElem,数组最大容量为maxSize,由于队列的入队操作只能在当前队列的队尾进行,而出队操作只能在当前队列的队首进行,所以需加上变量front和rear来分别指示队首队尾元素在数组中的位置,其初始值都为0,在非空队列中,front指向队首元素,rear指向队尾元素的下一个存储位置
假溢出:
从图3.16(d)可以看出,若此时还需要将数据元素H入队,H应该存放于rear=6的位 置处,顺序队列则会因数组下标越界而引起“溢出”,但此时顺序队列的首部还空出了两个数 据元素的存储空间。因此,这时的“溢出”并不是由于数组空间不够而产生的溢出。这种因 顺序队列的多次人队和出队操作后出现有存储空间,但不能进行人队操作的溢出现象称为”假溢出” 。 要解决“假溢出”问题,最好的办法就是把顺序队列所使用的存储空间看成是一个逻辑 上首尾相连的循环队列。当rear或 front到达 maxSize-1后,再加1就自动到0。这种转 换可利用Java语言中对整型数据求模(或取余)运算来实现,即令rear=(rear+1)% maxSize 。显然,当rear= maxSize-1时,rear加1后,rear的值就为0。这样,就不会出现 顺序队列数组的头部有空的存储空间,而队尾却因数组下标越界而引起的假溢出现象。
循环顺序队列类:
图中会发现一个问题:即循环顺序队列的判空和判满条件都是front==rear
解决循环顺序队列的队空和队满的判断问题常采用以下3种方法:
少用一个存储单元
设置一个标志变量
在程序设计过程中引进一个标志变量fag,其初始值置为0,每当入队操作成功后就置 flag=1;每当出队操作成功后就置fag=0,则此时队空的判断条件为: front==rear&&
flag==0,而队满的判断条件为:front==rear&&flag==1。
设置一个计数器
在程序设计过程中引进一个计数变量num,其初始值置为0,每当入队操作成功后就将 计数变量num的值加1;每当出队操作成功后就将计数变量num的值减1,则此时队空的 判断条件为:num==0,而队满的判断条件为:num>0&&front==rear
循环队列实现:
接口
1 2 3 4 5 6 7 8 9 10 package cn.dataStructure.SqQueue;public interface IQueue { public void clear () ; public boolean isEmpty () ; public int length () ; public Object peek () ; public void offer (Object x) throws Exception ; public Object poll () ; }
实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package cn.dataStructure.SqQueue;public class CircleSqQueueimpl implements IQueue { private Object[] queueElem; private int front,rear; public CircleSqQueueimpl (int maxSize) { front = rear = 0 ; queueElem = new Object[maxSize]; } @Override public void clear () { front = rear = 0 ; } @Override public boolean isEmpty () { return front == rear; } @Override public int length () { return (rear - front + queueElem.length) % queueElem.length; } @Override public Object peek () { if (front ==rear) return null ; else return queueElem[front]; } @Override public void offer (Object x) throws Exception { if ((rear+1 ) % queueElem.length == front) { throw new Exception("队列已满" ); } else { queueElem[rear] = x; rear = (rear+1 ) % queueElem.length; } } @Override public Object poll () { if (front == rear) { return null ; } else { Object t = queueElem[front]; front = (front + 1 ) % queueElem.length; return t; } } public void display () { if (!isEmpty()) { for (int i = front; i != rear; i= (i+1 )%queueElem.length) { System.out.print(queueElem[i].toString() + " " ); } }else { System.out.println("此队列为空" ); } } }
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package cn.dataStructure.SqQueue;public class CircleSqQueueTest { public static void main (String[] args) throws Exception { CircleSqQueueimpl circleSQ= new CircleSqQueueimpl(6 ); System.out.println("长度为:" + circleSQ.length() + "判空" + circleSQ.isEmpty()); circleSQ.display(); for (int i = 0 ; i < 5 ; i++) { circleSQ.offer(i); } circleSQ.display(); } }