绪论
数据结构的地位
在计算机相关专业的学习过程中,我们前期的课程主要是以学习语言基础为主,后期则是以软件架构设计,操作系统,软件工程相关的课程为主。而数据结构则是在中间起着承上启下的作用。
算法+数据结构=程序
数据结构的术语
数据 (Data) 可被计算机识别并加工处理的对象
数据元素(Element) 有数据组成的具有一定意义的基本单位
数据项(Item) 是组成数据元素的、不可分割的最小单位
数据对象(Data Object) 具有相同性质的若干个数据元素的结合
数据元素是组成数据的基本单位,数据项是组成数据元素的的基本单位,数据项是不可再分割的最小数据单位。但是在解决问题是,数据元素才是真正进行访问和处理的基本单位。
数据结构可以大致分为以下三块:
1、数据的逻辑结构
- 逻辑结构的二元组表示为:B=(D,R)
B:一种数据结构 , D:数据元素的集合, R:反应数据元素D之间的关系
- 关系用序偶<x,y>表示
x为第一元素,y为第二元素
x为y的前驱元素,y为x的后继元素
数据的逻辑结构可以分为线性结构和非线性结构
- 线性结构:数据元素之间存在一对一的关系 1:1
eg:线性表、栈、队列、串、数组······
- 非线性结构:集合结构:数据元素除了同属于一个集合外,没有其他的关系
eg: S={ 2,86,59,43,15}
树形结构:数据元素之间存在着一对多的关系 1:n
图结构:元素之间存在多对多的关系 m:n
2.数据的存储结构
存储结构(Storage Structure):数据及数据之间的关系在计算机内的存储方式,这是面向程序员
顺序存储结构(顺序存储)
对于顺序存储结构来说用下标来表示数据之间的关系
eg:数组
顺序存储结构中所有元素存放在一片地址连续的存储单元中
逻辑上相邻的元素在物理位置上也相邻,不需要额外空间表示元素之间的逻辑关系。
链式存储结构(随机存储)
数据元素存放在任意存储单元中,这组存储单元可以是连续的,也可以是不连续的。
通过指针域来反映数据元素的逻辑关系。
eg:单链表
用head唯一标识单链表
通过指针域来反映数据元素的逻辑关系
索引存储结构
建立附加的索引来标识数据元素的地址。他不是独立的存储结构,只是为了在查找运算是,能够减少查找的时间,提高数据查找的性能。如:手机通讯录在存储的同时可以按字母索引。
散列存储结构
又称为Hash存储,根据结点的关键字直接计算出该结点的存储地址。它是一种能快速实现访问的存储地址。它是一种能快速实现访问的存储方式,在理想情况下,无需比较即可根据指定值直接定位记录的存储位置。如:缓存存储、快速查找等。
数据的运算
数据的运算是对数据的操作,分为两个层次:运算描述和运算实现。
最常见的运算:
- 搜素运算:在数据结构中搜索满足一定条件的元素;
- 插入运算:在数据结构中插入新元素;
- 删除运算:将数据结构中指定元素删除;
- 更新运算:将数据结构中指定元素更行为新的元素;
同样的运算,在不同的存储结构中,其实现过程是不同的。
- 顺序结构的查找:下标
- 链式存储结构的查找:指针域
数据结构和数据类型
抽象数据类型=逻辑结构+抽象运算
抽象数据类型暂不考虑计算机的具体存储结构和运算的具体实现。
抽象数据类型实质上,就是描述问题本身(与计算机无关)
算法描述
算法可以用在自然语言、流程图、程序设计语言或者伪代码描述。
当一个算法用程序设计语言描述时,便成为程序。
程序和算法的最大区别:程序可以是无穷的,而算法必须满足有穷性。
算法的概念
算法是对特定问题求解方法和步骤的一种描述,是指令的有限序列。其中每个指令表示一个或多个操作。
算法的重要特性:
- 有穷性:算法必须在执行有限步之后终止。
- 确定性:算法的每条指令的有确切的定义,没有二义性。
- 可行性:算法的每一条指令都足够基本。
- 输入性:算法有零个或多个输入
- 输出性:算法有一个或多个输出
算法与程序
程序=数据结构+算法
算法分析
事后分析统计方法:编写算法对应程序,统计其执行时间。
事前估算分析方法:撇开上述因素,认为算法的执行时间是问题规模n的函数。
时间复杂度的分许方法
- 找出语句频度最大的那条语句作为基本语句
- 计算基本语句的频度得到问题规模n的某个函数f(n)
- 取其数量级用符合“o"得到
时间复杂度是由嵌套最深层语句的频度决定的
线性表
线性表是典型的线性结构
顺序表
相关的代码如下:
public class SqListClass<E> { //顺序表泛型类
final int initcapacity = 10; //顺序表的初始容量(常量)
public E[] data; //存放顺序表中元素
public int size; //存放顺序表的长度
private int capacity; //存放顺序表的容量
public SqListClass() { //构造方法,实现data和length的初始化
data = (E[]) new Object[initcapacity]; //强制转换为E类型数组
capacity = initcapacity;
size = 0;
}
//线性表的基本运算算法
public void CreateList(E[] a) { //由a整体建立顺序表
size = 0;
for (E e : a) {
if (size == capacity) { //出现上溢出时
updateCapacity(2 * size); //扩大容量
}
data[size] = e;
size++; //添加的元素个数增加1
}
}
public void Add(E e) { //在线性表的末尾添加一个元素e
if (size == capacity) { //顺序表空间满时倍增容量
updateCapacity(2 * size);
}
data[size] = e;
size++; //长度增1
}
public int size() { // 求线性表长度
return size;
}
public void Setsize(int nlen) { //设置线性表的长度
if (nlen < 0 || nlen > size) {
throw new IllegalArgumentException("设置长度:n不在有效范围内");
}
size = nlen;
}
public E GetElem(int i) { //返回线性表中序号为i的元素
if (i < 0 || i > size - 1) {
throw new IllegalArgumentException("查找:位置i不在有效范围内");
}
return (E) data[i];
}
public void SetElem(int i, E e) { //设置序号i的元素为e
if (i < 0 || i > size - 1) {
throw new IllegalArgumentException("设置:位置i不在有效范围内");
}
data[i] = e;
}
public int GetNo(E e) { //查找第一个为e的元素的序号
int i = 0;
while (i < size && !data[i].equals(e)) {
i++; //查找元素e
}
if (i >= size) { //未找到时返回-1
return -1;
} else {
return i; //找到后返回其序号
}
}
public void swap(int i, int j) { //交换data[i]和data[j]
E tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
public void Insert(int i, E e) { //在线性表中序号i位置插入元素e
if (i < 0 || i > size) { //参数错误抛出异常
throw new IllegalArgumentException("插入:位置i不在有效范围内");
}
if (size == capacity) { //满时倍增容量
updateCapacity(2 * size);
}
for (int j = size; j > i; j--) { //data[i]及后面元素后移一个位置
data[j] = data[j - 1];
}
data[i] = e; //插入元素e
size++; //顺序表长度增1
}
public void Delete(int i) { //在线性表中删除序号i位置的元素
if (i < 0 || i > size - 1) { //参数错误抛出异常
throw new IllegalArgumentException("删除:位置i不在有效范围内");
}
for (int j = i; j < size - 1; j++) { //将data[i]之后的元素前移一个位置
data[j] = data[j + 1];
}
size--; //顺序表长度减1
if (capacity > initcapacity && size == capacity / 4) {
updateCapacity(capacity / 2); //满足要求容量减半
}
}
@Override
public String toString() { //将线性表转换为字符串
String ans = "";
for (int i = 0; i < size; i++) {
ans += data[i].toString() + " ";
}
return ans;
}
private void updateCapacity(int newCapacity) { //改变顺序表的容量为newCapacity
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i <size; i++) {
newData[i] = data[i];
}
capacity = newCapacity; //设置新容量
data = newData; //仍由data标识数组
}
}
单链表
相关代码如下:
package Date;
class LinkNode<E>{
E data;
LinkNode<E>next;
public LinkNode(){
next=null;
}
public LinkNode(E d){
data=d;
next=null;
}
}
package Date;
public class LinkListClass<E> {//链表泛型类
public LinkNode<E> head;//存放头结点
public LinkListClass() {//构造方法
this.head = new LinkNode<E>();//创建头结点
this.head.next = null;
}
//线性表的基本运算算法
public void CreateListF(E[] a){//头插法
LinkNode<E> s;
for (int i = 0;i<a.length;i++){
s = new LinkNode<E>(a[i]);
s.next = head.next;
head.next = s;
}
}
public void CreateListR(E[] a){
LinkNode<E> s,t = head;
for (int i=0;i<a.length;i++){
s = new LinkNode<E>(a[i]);
t.next = s;
t=s;
}
t.next = null;
}
private LinkNode<E> geti(int i){
LinkNode<E> p =head;
int j = -1;
while (j<i){
j++;
p=p.next;
}
return p;
}
public void Add(E e){
LinkNode<E> s = new LinkNode<E>(e);
LinkNode<E> p = head;
while (p.next!=null){
p=p.next;
}
p.next=s;
}
public int size(){
LinkNode<E> p = head;
int cnt = 0;
while (p.next!=null){
cnt++;
p=p.next;
}
return cnt;
}
public void Setsize(int nlen){
int len = size();
if (nlen<0||nlen>len){
throw new IllegalArgumentException("设置长度:n不在有效范围内");
}
if (nlen==len){
return;
}
LinkNode<E> p = geti(nlen-1);
p.next = null;
}
public E GetElem(int i){
int len = size();
if(i<0||i>len-1){
throw new IllegalArgumentException("查找:位置i不在有效范围内");
}
LinkNode<E> p = geti(i);
return (E)p.data;
}
public void SetElem(int i,E e){
if (i<0||i>size()-1){
throw new IllegalArgumentException("设置:位置i不在有效范围内");
}
LinkNode<E> p = geti(i);
p.data = e;
}
public int GetNo(E e){
int j = 0;
LinkNode<E> p = head.next;
while (p!=null&&!p.data.equals(e)){
j++;
p=p.next;
}
if (p==null){
return -1;
}else {
return j;
}
}
public void swap(int i ,int j){
LinkNode<E> p = geti(i);
LinkNode<E> q = geti(j);
E tmp = p.data;
p.data = q.data;
q.data = tmp;
}
public void Insert(int i ,E e){
if (i<0||i>size()){
throw new IllegalArgumentException("插入:位置i不在有效范围内");
}
LinkNode<E> s = new LinkNode<E>(e);
LinkNode<E> p = geti(i-1);
s.next = p.next;
p.next = s;
}
public void Delete(int i){
if (i<0||i>size()-1){
throw new IllegalArgumentException("删除:位置i不在有效范围内");
}
LinkNode<E> p = geti(i-1);
p.next=p.next.next;
}
@Override
public String toString() {
String ans = "";
LinkNode<E> p = head.next;
while (p!=null){
ans+=p.data+"";
p=p.next;
}
return ans;
}
}
这块代码有一些单链表的操作包括了头插尾插的应用。
package Date;
public class LinkListExam {
public static void main(String[] args) {
//测试一:数据为整型
System.out.println("********测试1********");
Integer[] a = {1, 2, 3, 4, 5};
LinkListClass<Integer> L1 = new LinkListClass<>();
//1.尾插法创建链表
L1.CreateListR(a);
System.out.println("L1:" + L1);
System.out.println("L1长度=" + L1.size());
//2.在末尾添加元素
L1.Add(10);
System.out.println("L1:" + L1);
//遍历每个元素
System.out.println("求每个序号的元素值");
for (int i = 0; i < L1.size(); i++) {
System.out.println("序号" + i + "的元素值" + L1.GetElem(i));
}
//重置长度
System.out.println("重置长度为5");
L1.Setsize(5);
System.out.println("L1:" + L1);
//在指定位置插入元素
int i = 1;
Integer x = 20;
System.out.println("在序号"+i+"位置插入"+x);
L1.Insert(i,x);
System.out.println("L1:"+L1);
//删除指定位置的元素
i = 3;
System.out.println("删除序号" + i + "的元素");
L1.Delete(i);
System.out.println("L1:" + L1);
//替换指定位置的元素的值
i = 2;
x = 16;
System.out.println("设置序号" + i + "的元素值为" + x);
L1.SetElem(i, x);
System.out.println("L1:" + L1);
//获取序号为5的元素的序号
x = 5;
System.out.println("值为" + x + "的元素序号为" + L1.GetNo(x));
//测试2:数据为字符
System.out.println();
System.out.println("********测试2********");
Character[] b = {'a', 'b', 'c', 'd', 'e', 'f'};
LinkListClass<Character> L2 = new LinkListClass<>();
/*String str = "abcdef";
str.codePoints().mapToObj(ch->(Character)ch).toArray();
L2.CreateListR(str);
*/
L2.CreateListR(b);
System.out.println("L2:" + L2);
System.out.println("L2长度=" + L2.size());
L2.Add('x');
System.out.println("L2:" + L2);
System.out.println("求每个序号的元素值");
for (i = 0; i < L2.size(); i++) {
System.out.println("序号:"+i+"的元素值"+L2.GetElem(i));
}
//重置长度
System.out.println("重置长度为5");
L2.Setsize(5);
System.out.println("L2:" + L2);
//在指定位置插入元素
int j = 1;
Character l = 'k';
System.out.println("在序号"+j+"位置插入"+l);
L2.Insert(j,l);
System.out.println("L2:"+L2);
//删除指定位置的元素
j = 3;
System.out.println("删除序号" + j + "的元素");
L2.Delete(j);
System.out.println("L2:" + L2);
//替换指定位置的元素的值
j = 2;
l = 't';
System.out.println("设置序号" + j + "的元素值为" + l);
L2.SetElem(j, l);
System.out.println("L2:" + L2);
//获取序号为5的元素的序号
l = 'd';
System.out.println("值为" + l + "的元素序号为" + L2.GetNo(l));
}
}
双链表
package Date;
class DLinkNode<E> {
E data;
DLinkNode<E> prior;
DLinkNode<E> next;
public DLinkNode() {
prior = null;
next = null;
}
public DLinkNode(E d) {
data = d;
prior = null;
next = null;
}
}
这段代码中有俩个方法,注释掉的是另外一种写法
package Date;
public class DLinkListClass<E> extends DLinkNode<E> {
public DLinkNode<E> dhead;
private int size;
public DLinkListClass() {
dhead = new DLinkNode<E>();
dhead.prior = null;
dhead.next = null;
}
public void CreateListF(E[] a) {
DLinkNode<E> s;
for (int i = 0; i < a.length; i++) {
s = new DLinkNode<E>(a[i]);
s.next = dhead.next;
if (dhead.next != null) {
dhead.next.prior = s;
dhead.next = s;
s.prior = dhead;
}
}
}
// public void CreateListR(E[] a) {
// DLinkNode<E> s, t;
// t = dhead;
// for (int i = 0; i < a.length; i++) {
// s = new DLinkNode<E>(a[i]);
// t.next = s;
// s.prior = t;
// t = s;
// }
// t.next = null;
//
// }
//
//
// public void Insert(int i, E e) {
// if (i < 0 || i > size())
// throw new IllegalArgumentException("插入:位置i不在有效范围内。");
// DLinkNode<E> s = new DLinkNode<E>(e);
// DLinkNode<E> p = new geti(i - 1);
// s.next = p.next;
// if (p.next != null)
// p.next.prior = s;
// p.next = s;
// s.prior = p;
// }
//
// public void Delete(int i) {
if (i < 0 || i > size() - 1)
throw new IllegalArgumentException("删除:位置i不再有效范围内");
DLinkNode<E> p = geti(i);
if(p.next!=null)
p.next.prior=p.prior;
p.prior.next=p.next;
}
//
//
//
// public DLinkListClass<E> geti(int i) {
// DLinkNode<E> p = dhead;
// int j = -1;
// while (j < i) {
// j++;
// p = p.next;
// p.prior=p;
// }
// return (DLinkListClass<E>) p;
//
// }
//
//
// private class geti extends DLinkNode<E> {
// public geti(int i) {
// }
// }
}
循环链表
循环链表分为循环单链表和循环双链表
循环单链表泛型类
package Date;
public class CLinkListClass<E>{
LinkNode<E>head;
public CLinkListClass(){
head=new LinkNode<E>();
head.next=head;
}
}
循环双链表泛型
package Date;
public class CDLinkListClass <E>{
DLinkNode<E>dhead;
public CDLinkListClass(){
dhead=new DLinkNode<E>();
dhead.prior=dhead;
dhead.next=dhead;
}
}
链表容器——Linkedlist
在Java中list接口还有另外一个重要的实现类——Linkedlist类,它采用循环双链表存储对象序列,可以看成链式存储结构的表在Java语言中的实现。
相关代码如下:
package Date;
import java.util.ArrayList;
import java.util.LinkedList;
public class Chapter2_3_7 {
public static void main(String[]args){
ArrayList <String>myarrlist=new ArrayList<String>();
myarrlist.add("A");
myarrlist.add("B");
myarrlist.add("C");
myarrlist.add("D");
System.out.println("ArrayList:"+myarrlist);
System.out.println("ArrayList->LinkedList");
LinkedList<String>mylinklist=new LinkedList<String>(myarrlist);
System.out.println("LinkedList:"+mylinklist);
System.out.println("清空LinkedList并添加1,2,3");
mylinklist.clear();
mylinklist.add("1");
mylinklist.add("2");
mylinklist.add("3");
System.out.println("LinkedList:"+mylinklist);
myarrlist=new ArrayList<String>(mylinklist);
System.out.println("LinkedList->ArrayList");
System.out.println("ArrayList:"+myarrlist);
}
}