- 总结《大话数据结构》写个教程记录供日后方便查看
1.线性表定义:零个或多个相同的数据元素的组成有限序列
简单说明下:线性表是一个序列,元素之间有顺序。第一个元素无前驱,最后一个元素无后继,其他元素有且只有一个前驱和后继。并且数据类型相同。
在较复杂的线性表中,一个数据元素可以由若干个数据项组成。
2.线性表存储结构
2.1.顺序存储结构
2.1.1顺序存储定义
用一段地址连续的存储单元依次存储线性表的数据元素。物理结构上连续
2.1.2顺序存储方式
在内存中,申请一块连续空间,按照顺序进行存储的方式。说白了,就是在内存中找块地,通过占位的方式,把一定的内存空间给粘了,然后把相同数据类型的数据元素依次放在这块空地。用一维数据来实现顺序存储结构,把第一个数据元素存到数组下标为0的位置中。
C语言线性表的顺序存储的结构代码
#define MACSIZE 20 //存储空间初始分配量
typedef int ElemType //ElemType类型根据实际情况而定,这里假设为int
typedef struct{
ElemType data[MAXSIZE] ;//数组存储元素
int length;//线性表当前长度
}
顺序存储结构三个属性:
1.存储空间的起始位置:数组data
2.线性表的最大存储容量:数组长度Maxsize
3.线性表的当前长度:length
JAVA顺序存储结构
import java.util.Arrays;
/**
* 线性表的顺序存储结构
*/
public class SqList{
private int maxSize;//数组长度,即最大存储空间
private int ElemType;/* ElemType类型根据实际情况而定,这里假设为int*/
private int length;/*线性表当前长度*/
private Object[] data;//存储数据元素
public SqList(){
//初始化,默认为20
maxSize=20;
data=new Object[20];
length=0;
}
/**
* 获取length
*/
public int getlength(){
return length;
}
/**
* 自定义长度
* @param maxSize
*/
public SqList(int maxSize){
if(maxSize<0){
throw new RuntimeException("数据大小为负,初始化失败!");
}else {
this.maxSize=maxSize;
data=new Object[maxSize];
length=0;
}
}
/**
* 对元素为空进行判断
* 判断元素是否存在
*/
public boolean containelem(Object elem){
if(elem!=null){
return false;
}
for(int i=0;i<length;i++){
if(data[i].equals(elem)){
return true;
}
}
return false;
}
/**
* 1.取出位置不符合,抛异常
* 2.取出元素
* @param i 是下标索引
* @return
*/
public Object getelem(int i){
if(i>length&&i<0){
return "error";
}
return data[i];
}
/**
*1.删除位置不合理,抛异常
*2.取出元素
* 3.从删除元素位置开始遍历到最后一个位置,分别将他们像前移动一个位置
* 4.表长减1
*/
public String deletesql(int i,Object elem){
if(length==0){
return "Error";
}
if(i>length&&i<1){
return "error";
}
if(i<length){
for(int j=i;j<length-1;j++){
data[j-1]=data[j];
}
length--;
}
return "OK";
}
/**
* 初始化条件顺序表存在,1<i<<length;
* 操作结果,返回第i个元素值
* @param i
* @param elem
* @return
*/
public String GetElem(int i,Object elem){
if(i>maxSize&&i<0){
return "Error";
}
elem=data[i-1];
return "ok";
}
/**
* 插入算法思路:
* 1.插入不合理,抛出异常
* 2.如果线性表长度大于或等于数组长度,抛出异常,或者动态加入容量
* 3.从最后一个位置开始向前遍历到第一个位置,分别将他们向后移动一位
* 4.将要插入元素填入位置i处
* 5.表长加1
*
*注意参数i是从1开始的。
*
*
* @param elem
*/
public String insertElem(int i,Object elem){
if(length==maxSize){
return "Err";
}
//1.
if(i>length+1||i<1){
return "err";
}
//3.
for(int j=length;j<i-1;j--){
if(j>i-1){
data[j+1]=data[j];
}
}
data[i-1]=elem;
//5
length++;
return "OK";
}
/**
* 在数组data后面插入某个元素
* 注意:elem 不能为空,length 不能超过maxsize
* 思路:添加元素
* length加1
* @return
*/
public boolean insertElem(Object elem){
if(data==null){
return false;
}
if(length<maxSize){
data[length]=elem;
length++;
return true;
}
return false;
}
public void print(){
System.out.println(Arrays.toString(data));
}
public int getMaxSize() {
return maxSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
}
/**
* 两个链表求交集 A U B
* 思路:
* 遍历B链表,查看A链表中是否存在这个元素,不存在,插入这个元素
* 注意顺序链表A 初始化内容是A+B之和
*/
public class Union {
public void initunion(SqList a,SqList b){
for (int i=0;i<b.getlength();i++){
Object obj=b.getelem(i);
if(!a.containelem(obj)){
//数组长度不能超过链表长度
if(a.getlength()<a.getMaxSize()){
a.insertElem(obj);
}
}
}
}
}
public class Main {
public static void main(String[] args) {
SqList sqList=new SqList();
for (int i=0;i<10;i++){
sqList.insertElem(i+1,i);
}
SqList sqList2=new SqList();
for (int i=10;i<15;i++){
sqList2.insertElem(i);
}
// sqList.print();
// sqList2.print();
// Object t=new Object();
// sqList.deletesql(2,t);
// if(t instanceof Integer){
// System.out.println((Integer)t);
// }
// sqList.print();
// Object getobj=sqList.getelem(3);
// if(getobj instanceof Integer){
// System.out.println((Integer)getobj);
// }
Union union=new Union();
union.initunion(sqList,sqList2);
sqList.print();
sqList2.print();
}
}
时间复杂度:
最好的情况,如果元素插入到最后一个位置,或者删除最后一个位置,此时的时间复杂度为O(1),因为不需要移动元素。如同来了一个新人要正常排队,当然是排到最后,如果此时他想离开,不会影响其他人,最坏的情况是插入或者删除第一个元素,此时需要移动n-1次。由于元素插入到第i个元素,或者删除第i个元素,需要移动n-i次,平均移动n-2/2.
由此得出结论,平均时间复杂度O(n)
优缺点:
优点:无需为表示逻辑关系添加额外的存储空间,可以快速查询任何位置的元素
缺点:插入和删除操作需要移动大量的元素。难以确定长度,造成空间碎片。
2.2链式存储结构
特点
用一组任意的存储单元存储线性表的数据元素,这组存储可以是连续的,也可以是不连续的。这就意味着,这些数据元素可以存在内存未被占用的任意位置。
定义:
为了表示每个数据元素ai与直接后继数据元素ai+1之间的逻辑关系对数据元素ai来说,除了存储其本身的信息之外,还需要存储一个指示其直接后继的信息。存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域,指针域中存储的信息称为指针或链。这两部分信息组成数据集元素ai的存储映像,称为结点(Node)。
n个结点(ai的存储映像)链结成一个链表,即为链表(a1,a2,.....an)的链表式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
链表中的第一个结点的存储位置叫头指针,那么正规链表的存取就必须从头指针开始进行。之后的每个结点,其实就是,就是上一个后继指针指向的位置。
最后一个,就意味着后继不存在,线性链表的最后一个结点指针为“空”
头指针与头结点的异同
头指针:
头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。
头指针具体标识作用,所以常用头指针冠以链表的名字
无论链表是否为空,头指针均不能为空,头指针是链表的必要元素
头结点:
头结点是为了操作的统一和方便而设立的,放在第一个元素结点之前,其数据域一般无意义
有了头结点,对在第一个元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一
头结点不一定是链表必须要素
线性表链式存储结构代码描述
若线性表为空,则头结点的指针域为“空”。
如图表示线性表中的数据元素及数据元素之间的逻辑关系。
2.2.1单链表
单链表中,结构指针
typede struct Node{
ElemType data;
struct Node * next;
}Node;
typedef struct Node *LinkList;//定义LinkList
Java代码
/**
* 单链表的存储结构
*/
public class LinkList {
private NodeSql head;//定义头结点
public LinkList() {
head = new NodeSql();//
}
/**
* 获取链表第一个数据的算法思路
* 1.声明一个节点p,初始化j=1
* 2.
* 3.j<i 结点向后移动,j+1
* 4.若p到到末尾没有找到元素,表示元素不存在
* 5.返回指定元素
*
* @param i
* @param elem
*/
public Object getElem(int i, Object elem) {
NodeSql p;//指向第一个元素
int j=1;
p=head;
while (p.next!=null&&j<i){
p=p.next;
++j;
}
elem=p.data;
return elem;
}
/**
* 思路:在i位置插入元素
* 1.声明一个节点p,初始化讲
* 2.p指向第一个结点‘
* 3.p不为空j<i,进行移动 p=p.next j++
* s.next=p.next
* p.next=s
*/
public void insertelem(int i, NodeSql s) {
NodeSql p;//初始化一个结点p,指向第一个结点
int j=1;
p=head;
while (p.next!=null&&j<i){
p=p.next;
++j;
}
if (p==null||j>i) return;
s.next=p.next;
p.next=s;
}
/**
* 添加结点
* 为空判断
* @param elem
*/
public void addNode(NodeSql elem){
NodeSql temp=head;
while (temp.next!=null){
temp=temp.next;
}
temp.next=elem;
}
public void print(){
NodeSql temp=head;
while (temp!=null){
System.out.println(temp.data);
temp=temp.next;
}
}
/**
* 删除结点
* 初始化j=1
* 循环 找到第i个结点
* 找到某个结点
* 删除p->next=p->next->kkonext
* @param i
* @return
*/
public boolean deleteelem(int i){
NodeSql p;
int j=1;
p=head;
while (p.next!=null&&j<i){
p=p.next;
++j;
}
if(p==null||j>i){
return false;
}
p.next=p.next.next;
return true;
}
}
2.2.2 静态链表
特点:
用数组替代指针,描述单链表
定义:数组的元素由两个数据域组成,data和cur。游标cur相当单链表中next,存放该元素的后继在数组中的下标。用数组描述的链表叫静态链表。
结构:
2.2.3 循环链表
定义:将单链表中终端结点的指针端由空指针改为指向头指针,就使得整个单链表形成一个环,这种头尾相接的
结构:
/**
* 循环单链表
*/
public class CilcleList {
private NodeSql head,rear;//头、尾结点
private int size=0;
public CilcleList(){
head=new NodeSql();
rear=head;
rear.next=head;//尾结点指向头指针
}
/**
* 思路:初始化变量p ,p 指向头结点,
* 初始化变量j=1;
* 判断是否p->next!=rear j<i 找到i的位置,p=p->next
* new 一个结点Nodesql s,将值赋值进去
* s.data=elem
* s.next=p.next
* p.next=s;
* return ok
* @param i
* @param elem
*/
public boolean addList(int i,Object elem){
NodeSql p;
int j=1;
p=head;//p为头结点
while (p.next!=rear&&j<i){
p=p.next;
++j;
}
if(size>0){
//p.next=rear说明指向最后一个结点,
if(j>i){
return false;
}
}
NodeSql s=new NodeSql(elem);
s.next=p.next;
p.next=s;
size++;
return true;
}
/**
* 获取第i个位置的元素,返回elem中
* 思路:1.初始化结点p 和j=1
* 2.判断p.next!=rear j<i,移动j元素,找到第i个位置,p=p.next,j++,
* 3.返回元素 elem=p.data
* @param i
* @param elem
*/
public void getElem(int i,Object elem){
NodeSql p;
int j=1;
p=head;
while (p.next!=rear&&j<i){
p=p.next;
j++;
}
if(p.next==rear||j>i){
return;
}
elem=p.data;
}
/**
* 获取所有的元素
* 1.初始结点p
* 2.判断p.next!=rear,遍历链表p=p.next,打印值
*/
public void getallelem(){
NodeSql p;
p=head;
while (p.next!=rear){
p=p.next;
System.out.print(p.data);
}
}
/**
* 删除第i个位置的元素
* 思路:
* 1.初始化p j=1;
* 2.p指向头结点
* 3.判断p.next!=rear j<i找到第i个结点位置p=p.next j++
* p.next=p.next.next
* 4.清除结点
* @param i
* @return
*/
public boolean deleteElem(int i){
NodeSql p,q=null;
int j=1;
p=head;
while (p.next!=rear&&j<i){
p=p.next;
j++;
}
if(j>i){
return false;
}
q=p.next;
p.next=q.next;
q=null;
return true;
}
/**
* 合并两个链表
* 思路:
* 找到 pr 为this 的尾指针reara,头结点p=reara.next,
* 找到qr的尾指针,那么rerab的头结点为rerab=qr.next
* pr 的尾结点指向qr的头结点
* reara.next=rerab.next.next/指向qr的第一个结点,不是头结点
* rearb.next=p;
* @param
* @param qr
*/
public void union(CilcleList qr){
NodeSql p;
NodeSql reara=this.rear;//pr的尾结点
NodeSql rearb=qr.rear;//qr的尾结点
p=reara.next;//ra的头结点
reara.next=rearb.next;
rearb.next=p;//将原a的头结点,赋值给 rearb.next
}
}
2.2.4双向链表
定义:双向链表(double linked list)是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。
特点:双向链表是单链表扩展出来的结构,所以很多操作和单链表相同。双向链表可以做反向遍历查找等数据结构。
/**
* 约瑟夫问题:
* * 描述:n个小朋友按编号1、2、3.......n围成 一个圈坐着,按照顺时针方向从K的位置,从1开始依次报数,数到m位置的小朋友出局,
* 再从m后面一个小朋友依次数数。直到所有的小朋友出局
* * 思路:第一步:创建一个具有n个结点不带头结点的双向循环链表代码1到n个小朋友
* 2.找到k个位置的人从1开始报数,找到第k个结点
* 3.编号为k的人开始报数,并开始报数,报到m开出列,即删除该结点
*/
public class YueSefu {
private DulNode head;
private int size = 0;
public YueSefu() {
}
public void addElem(int i, Object elem) {
int j = 0;
DulNode p = null;
if (size == 0) {//第一个结点
head = new DulNode(elem);
p = head;
head.prior=head;
head.next = p;//尾结点指向头结点
head.flag=true;
return;
}
while (p.next != head && j < i) {
p = p.next;
j++;
}
if (p == null || j > i) {
return;
}
DulNode s = new DulNode(elem);
s.prior = p;
s.next = p.next;
p.next.prior = s;
p.next = s;
}
/**
* 2找到第k个结点
* 思路:初始化p j=1;
* 进行查找k个之后开始计数
*
* @param k
*/
public void findindex(int k, int m) {
DulNode p;
p = head;
int j = 1;
int cont = 1;
if (k > 1) {
while (p!=null) {
//找到k的位置,开始计算
if (j < k) {
p = p.next;
j++;
}else {
if (cont < m) {
p = p.next;
cont++;
} else {
cont = 1;
//开始移除某个元素
// deleteelem(p.data);
getAllElem();
System.out.println(" 需要删除的 "+p.data);
//重新定位头结点
if(p==head){
head=p.next;
}
// 移除某个结点
p.next.prior=p.prior;
p.prior.next=p.next;
if(p.next==p&&p.next.prior==p){
p=null;
}
}
}
}
}
}
/**
* 移除某个结点
*
*/
/**
* 移除某个位置的元素
* 思路:初始化p=head,
*
* @param i
* @return
*/
public boolean deleteElem(int i) {
DulNode p;
int j = 1;
p = head;
while (j < i) {
p = p.next;
j++;
}
if (j > i) {
return false;
}
p.prior.next = p.next;
p.next.prior=p.prior;
return true;
}
/**
* 移除某个元素
*/
public boolean deleteelem(Object elem){
DulNode p;
p=head;
while (p!=null){
if((p.data).equals(elem)){
p.next.prior=p.prior;
p.prior.next=p.next;
return true;
}if(p.next==p&&p.next.prior==p){
p=null;
return false;
}
else {
p=p.next;
}
}
return false;
}
/**
* 添加n个结点
* 思路:初始化n个结点
*
* @param n
*/
public void addAllElem(int n) {
DulNode p = null;
if (size < n) {
int j = 1;
while (j <= n) {
if (size == 0) {
head = new DulNode(j);
head.prior=head;
head.next = head;
head.prior=head;
p = head;
j++;
size++;
} else {
DulNode s = new DulNode(j);
s.prior = p;
s.next = p.next;
p.next.prior = s;
p.next = s;
p = s;
j++;
size++;
}
}
}
}
/**
* 遍历元素
*/
public void getAllElem() {
DulNode p = head;
while (p!=null) {
System.out.print(" "+p.data);
if (p.next==head){
p=null;
}else {
p = p.next;
}
}
}
}
单链表的例子
/**
* 单链表的应用
* 利用单链表就两个集合的差集A-B ,即所有的属于集合A而不属于集合B的元素。
* 思路:1.初始化单链表A
*/
public class Chaji {
private NodeSql head;
private static int size;
public Chaji(){
size=0;
}
/**
* 尾部进行插入
*
* @param elem
*/
public void addHeadElem(Object elem) {
if (elem != null) {
NodeSql p;
p = head;
if (size == 0) {
head = new NodeSql(elem);
size++;
} else {
while (p.next != null) {
p = p.next;
}
NodeSql s = new NodeSql(elem);
p.next = s;
size++;
}
}
}
/**
* 求差价
* 时间复杂度()
*/
public void ChaJiElem(Chaji lb) {
//遍历链表lb,la看是否存在,如果存在删除该元素
NodeSql p=lb.head;
//遍历lb链表
while (p!=null){
//1.判断lb元素是否存在la链表中
if(IsExsitElem(p.data)){
deleteelem(p.data);
}
if (p.next==null){
p=null;
}else {
p=p.next;
}
}
}
/**
* 遍历表判断元素是否存在在链表中
*
* @param elem
* @return
*/
public boolean IsExsitElem(Object elem) {
NodeSql p;
p = head;
if (elem == null) {
return false;
}
while (p.next != null) {
if ((p.data).equals(elem)) {
return true;
}
p = p.next;
}
return false;
}
/**
* 删除某个元素
*/
public boolean deleteelem(Object elem){
NodeSql p;
p=head;
while (p.next!=null){
//删除某个元素
if((p.next.data).equals(elem)){
p.next=p.next.next;
return true;
}
p=p.next;
}
return false;
}
/**
* 删除某个结点
* 删除某个元素
*/
public boolean deleteNode(NodeSql node){
return false;
}
public void getAllElem() {
NodeSql p;
p = head;
while (p != null) {
System.out.print(p.data+" ");
if (p.next==null){
p=null;
}else {
p = p.next;
}
}
}
}
循环单链表demo
/**
* 已知一个带哨兵结点h的循环链表中的元素,有正数和负数,
* 试着编写一个算法,构造两个循环单链表,一个单链表只含正数,一个单链表只含负数。
* 分析:初始时,先创建两个空单链表ha和hb,然后依次遍历链表,如果是正数插入到ha中,如果是负数插入到hb中
*/
/**
* 步骤:1.先初始化一张循环链表h,既有正数又有负数
* 2.初始化两个空链表ha,hb
* 3.遍历链表h
*/
public class LinkListDemo {
private NodeSql head;//定义一个头结点也就是哨兵h
private int size = 0;
public LinkListDemo() {
head = new NodeSql();//定义头结点
head.next = head;//循环链表
}
/**
* 进行头插入数据元素
* 思路:把元素依次插入
*/
public void addHead(Object elem) {
if (elem != null) {
NodeSql p;
p = head;
int j = 0;
while (p.next != head && j < size) {
p = p.next;
}
NodeSql s = new NodeSql(elem);
//把s插入链表中
s.next = head;
p.next = s;
size++;
}
}
public void getAllElem() {
NodeSql p;
p = head.next;
while (p != null) {
System.out.print(p.data+" ");
if (p.next == head) {
p = null;
} else {
p = p.next;
}
}
}
/**
*遍历当前链表,负数插入到hb,正数插入到ha
* @param ha
* @param hb
*/
public void analyse(LinkListDemo ha,LinkListDemo hb){
NodeSql p;
p=head;
while (p!=null){
Object obj=p.data;
if(obj instanceof Integer){
Integer tb= (Integer) obj;
if(tb<0){
hb.addHead(tb);
}else {
ha.addHead(tb);
}
}
if (p.next==head){
p=null;
}else {
p=p.next;
}
}
}
}