首页 > 技术文章 > 数据结构课堂笔记

zzyh 2020-04-23 15:52 原文

template<class T>
void SeqList<T>::reSize(int newsize)
{
    if(newSize!=maxSize)
    {
        T *newarray=new T[newSize];
       /*
        开辟大小为newSize的内存空间
        数据类型为T        
        */
       if(newarray==NULL)
       {
        cerr<<"分配存储失败!"<<endl;
        exit(1); 
       }
    }
    int n=last+1;
    T *scrptr=data;
    T *destptr=newarray;
    while(n--) *destptr++=*srcptr++;
    delete []data;
    data=newarray;
    maxSize=newSize; 
}


template<class T>
int SeqList<T>::search(T &x) const
{
    for(int i=0;i<=last;i++)
    {
        if(data[i]==x) return i+1;
    }
}

/*
在第i个位置插入x 
插入前:a1,....,ai-1,ai,...,an
插入后:a1,...,ai-1,x,ai,...,an
ai-1和ai之间的逻辑发生了变化
表满:last>=MaxSize-1
last是最后一个元素数组下标
1<=i<=last+2
插入元素时移动的越少效率越高
移动元素的个数不仅和表长有关而且与插入位置有关 
最好情况(i=n+1)基本语句执行0次,时间复杂度O(1) 
*/

/*
顺序表的实现——删除
每个往前移一位 相当于覆盖掉了
首先要判断删除位置是否合法 
1<=i<=last+1 
*/
template<class T>
bool SeqList<T>::Remove(int i,T &x)
{
    if(last==-1||i<1||i>last+1) return false;
    x=data[i-1];//第i个元素数组下标是i-1
    for(int j=i;j<=last;j++)
    {
        data[j-1]=data[j];//后面覆盖前面 
    }
    last--; //表长-1 
} 


//顺序表的输入算法
template<class T>
void SeqList<T>::input()
{
    cout<<"输入元素个数";
    while(1)
    {
        cin>>last;
        if(last<=maxSize-1) break;
        cout<<"个数输入有误"; 
    }
    for(int i=0;i<=last;i++)
    {
        cin>data[i];
        cout<<i+1<<endl; 
    } 
}
 
//顺序表的输出算法
template<class T>
void SeqList<T>::output()
{
    cout<<"当前元素的最后位置为"<<last<<endl;
    for(int i=0;i<=last;i++)
    {
        cout<<"#"<<i+1<<":"<<data[i]<<endl;
    }
}

//顺序表的应用

//并运算 
void Union(SeqList<int>&A,SeqList<int>& B)
{
    int n=A.length(),x;
    int m=B.length();
    for(int i=1;i<=m;i++)
    {
        B.getData(i,x);//在B中取一个元素 
        int k=A.Search(x);//在A中搜索它 
        if(k==0)     //若未找到插入到A的最后 把并的结果放入A中(若重新开空间则浪费) 
        {A.Insert(n,x);n++;} 
    }
} 

//交运算
void Intersection(SeqList<int>&A,SeqList<int>&B)
{
    int n=A.Length();
    int m=B.Length();int i=1,x;
    while(i<=n)
    {
        A.getData(i,x);
        int k=B.search(x);
        if(k==0) A.Remove(i,x),n--;
        else i++;
    }
} 

//顺序表的优点:
/*
无需为表示表中元素之间的逻辑关系而增加额外的存储空间
随机存取 可以快速地存取表中任一位置的元素
顺序表的缺点:
(1)插入和删除操作需要移动大量元素
(2)表的容量难以确定,表的容量难以扩充
(3)造成存储空间的碎片

链表非常适合频繁地插入或删除、存储空间需求变化很大地情形 
*/ 


/*
单链表是最简单地链表,也叫线性链表,它用指针表示节点间地逻辑关系
Node 存有数据域和指针域
线性结构first为头指针
结点可以连续可以不连续存储  99.99%不连续存储 
节点的逻辑顺序和物理顺序可以不一致
表扩充很方便 
*/ 

/*
顺序表 容量是固定
单链表  不固定  
*/ 

/*
多个类表达一个概念(单链表)
链表节点(ListNode)类 
链表(List)类 
*/ 

// 1.复合类1 
class List;//链表类定义(复合方式)
class LinkNode
{
    friend class List;//链表类为其友元类
    private:
        int data;
        LinkNode *link; 
};
class List
{
    private:
        LinNode *first;
};
//友元类不具有对称性??
//复合类2:用struct定义linkNode类
struct LinkNode
{
    int data:
    LinkNode *link;
};
class List
{
    private:
        LinkNode *first;
    public:
};
/*
虽然结构使List失去了封装性
但是所有属于LIst对象地LinkNode结点只能用first进行访问。 
*/ 
//2、嵌套类
class List
{
    
}  
 
 //3、继承方式  
class LinkNode
{
     protected:
         int data;
         LinkNode *link;
};
class List
{
    
}
//写不完了  自己去看PPT吧。 
1

 

/*
单链表的链接存储结构及实现
单链表:
头指针:指向第一个结点的地址
尾标志:终端节点的指针域为空
空表:first=NULL
非空表:构造一个头结点

插入:
(1)第一个结点前插入:
newnode->link=first;
first=newnode;
(2)在中间插入:
用p代表ai 
先修改后继指针 newnode->link=p->link
p->link=newnode 
(3)在链表末尾插入:
newnode->link=p->link
p->link=newnode

删除:
(1)删除表中第一个元素 
del=first; first=first->link;delete del ;
(2)在单链表中或表尾
用p指向被删结点的前一个结点
del指向被删结点
p->link=del->link
delete del; 
 
头结点:为了统一操作,在表最前端设置附加头结点,
本身不带数据,仅标纸为表头 
*/

//附加头节点的单链表类

template<class T>
struct LinkNode
{
    T data;
    LinkNode<T> *link;
    LinkNode(LinkNode<T>*ptr=NULL)
    {
        link=ptr;
    }//构造函数
    LinkNode(const T& item,LinkNode<T>*ptr)
    {
        //
    } 
} 

template<class T>//链表类 
class List:public LinearList<T>
{
    protected:
        LinkNode<T>*first;//头指针
    public:
    List(){first=new LinkNode<T>;}
    List(const T&x)
    {
        first=new LinkNode<T>(x);
     } 
     List(List<T>&L);
     ~List(){makeEmpty();}
     void MakeEmpty();
     //???????
} 


LinkNode<T>*getHead()const{return first;}
LinkNode<T>*Search(T x);//返回LinkNode型指针 
//搜索含数据x的元素
LinkNode<T>*Locate(int i);
//搜索第i个元素的地址
bool GetData(int i,T&x);
//取出第i个元素的值
bool Insert(int i,T &x);
//将x插在第i个元素后
bool Remove(int i,T &x);
//删除第i个元素,x返回该元素的值。
bool IsEmpty()const
{return first->link==NULL?true:false;}
bool IsFull()const{return false;}
//链表不会满的
void Sort();
void input();
void output();
List<T>& operator=(List<T>& L);
//链表赋值直接用等号

template<class T>
List<T>::List(List<T>&L) //复制构造函数 
{
    T value;
    LinkNode<T>*srcptr=L.getHead();
    LinkNode<T>*destptr=first=new LinkNode<T>;
    while(srcptr->link!=NULL)
    {
        value=srcptr->link->data;
        destptr->link=new LinkNode<T>(value);
        destptr=destptr->link;
        srcptr=strptr->link; 
    } 
    destptr->link=NULL;
}//????/

template<class T>9
void List<T>::MakeEmpty
{
    //删去除附加头节点外的所有其他结点 
    LinkNode<T>*q;
    while(first->link!=NULL)
    {
        q=first->link;
        first->link=q->link;
        //将表头结点后第一个结点q从
        //链中摘下 
        delete q;//释放它 
     } 
} 
 
 
template<class T>
int List<T>::Length()const
{
    LinkNode<T>*p=first->link;
    int count=0;
    while(p!=NULL)
    {
        p=p->link;
        count++;
    }
    return count;
 } 
 
template<class T>
LinkNode<T>*List<T>::Locate(int i)
{
    //定位函数 返回第i个元素的地址
    if(i<0) return NULL;
    LinkNode<T>*p=first;
    int k=0;
    while(p!=NULL&&k<i)
    {
        p=p->link;k++;//k为计数器 
    }
    return p; 
 } 
 //-----------------------------
 //插入 先修改后继 
 s=new Node<T>;
 s->data=x;
 s->link=p->link;
 p->link=s; 
 
 /*
 有了表头
 每个结点都有了前驱结点
 直接插 
 
 插入伪代码“
 1、工作指针p初始化
 2、找到第i-1个结点并使工作指针p指向该结点
 3、若查找不成功,则插入位置不合理, 
 ... 
 */ 
 
 template<class T>
bool LinkList<T>::Insert(int i,T &x)
{
    //在第i个位置后插入元素x 
     //?????????????
     //i到底是数组下标还是第i个数据 
     LinkNode<T> *p=Locate(i-1);
     if(p==NULL) return false;
     else
     {
         s=new LinkNode<T>(x);
         s->link=p->link;
        p->link=s;
        return true;  
    }
} 
 
//删除是直接删除第i个元素
//用临时变量保存被删结点、
//取值,摘链 
//删除不能删最后一个元素
/*
表尾的特殊情况:
虽然被删结点不存在
但其前驱结点却存在 
最后一个结点虽然有前驱 但是不存在 
*/ 
bool List<T>::Remove(int i,T &x)
{
    /*
      q=p->link;
      x=q->data;
      p->link=q->link;
      delete q;
    */
} 

/*
 1、初始化p
 2\查找第i-1个结点并使p指向该结点
 3.若p不存在或p不存在后继结点,则不可删/
 3.1暂存被删元素
 3.2摘链
 3.3释放被删结点
 3.4返回被删元素值 
*/ 

//删除代码
template<class T>
bool List<T>::Remove(int i,T &x)
{
    LinkNode<T>*p=Locate(i-1);
    if(p==NULL||p->link==NULL) return dalse;
    LinkNode<T>*q=p->link;
    x=q->data;
    p->link=q->link;
    delete q;
    return true;
} 

//两个链表直接重载赋值
template<class T>
List<T>&List<T>::Operator=(List<T>&L)
{
    T value;
    LinkNode<T> *srcptr=L.getHead();
    LinkNode<T> *destptr=first=new LinkNode<T>;
    while(srcptr->link!=NULL)
    {
        value=srcptr->link->data;
        destptr->link=new LinkNode<T>(value);
        destptr=destptr->link;
        srcptr=strptr->link;
    }
    destptr->link=NULL;
    return *this;
} 

//输入链表
//前插法  物理顺序和逻辑顺序相反 
template<class T>
void List<T>::inputFront(T endTag)
{
    LinkNode<T>*newNode;T val;
    makeEmpty();cin>>val;
    while(val!=endTag)
    {
        newNode=new LinkNode<T>(val);
        //new 可能申请不成功 所以下面要判断 
        if(newNode==NULL)
        {
            cerr<<"error"<<endl;
            exit(1);
        }
        newNode->link=first->link;
        first->link=newNode;
        cin>>val;
    }
 } 
//后插法 
template<class T>
void List<T>::inputRear(T endTag)
{
    LinkNode<T>*newNode,*last,T val;
    makeEmpty();cin>>Val;last=first;
    while(val!=endTag)
    {
        newNode=new LinkNode<T>(val);
        if(newNode==NULL)
        {
            cerr<<"error"<<endl;
            exit(1);
        }
        last->link=newNode;
        last=newNode;
        cin>>val; 
    }
 } 
 
 /*
 算法设计的一般步骤
 1.确定入口(已知条件) 出口(结果)
 2.画示意图
 3.
 (1)正向思维: 选定一个思考问题的起点 提出问题 解决问题
 (2)逆向思维:从结论出发
 (3)正逆结合
4.写出顶层较为抽象算法,分析边界情况
5.验证第四步的算法
6.写出具体的算法
7.进一步验证 手工运行 

*/
/* 
单链表中:
(1)除非已知first否则无法找到其他所有节点
(2)单链表里 找后继容易 找前驱难
为了解决:
1.单向循环链表   循环链表 
首尾相连 最后一个结点的后继为第一个结点
空表:first->link=first
开始节点:first->link;
终端节点:将单链表扫一遍 O(n)
p!=NULL // p!=first(循环链)
p->link!=NULL//p->link!=first
带尾指针的循环链表:
开始结点: last->link->link
终端节点:last 

*/

template<class T>
class CircList:public LinearList<T>
{
    private:
        CircListNode<T>*first,*last;
    public:
        CircList(const T&x);
        CircList(CircList)<T>
        //--------------
} 
 
 //循环链表插入
bool CircList<T>::Insert(int i,T x)
{
     p=first;count=0;
     while(p!=first&&count<i-1)
     {
         p=p->next;
         count++;
     }
     if(p==NULL) return false;
     else
     {
         s=new CircLinkNode<T>;
         s->data=x;
         s->link=p->link;
         p->link=s;
     }    
} 
2.双向循环链表 
 
2

 

/*
2、从被调用函数返回调用函数之前,应该完成“
1)保存被调用函数的计算结果
2)释放被调用函数的数据区
3)依照被调用函数保存的返回地址将控制转移到调用函数 
当多个函数嵌套调用时,由于函数运行的规则是:后调用先返回,
因此各个函数的存储管理应实行"栈式管理“ 

总之:函数调用后调用先返回,实行栈式管理。 

*/ 

int main()
{
    A(5,6);
    return 0;
} 

void A(int a,int b)
{
    B();
}

void B()
{
    
}
/* 
下面是一个栈:
 
函数B占有存储区 ----栈顶 
函数A占有存储区 
主函数占有存储区 ---栈顶 
*/
/* 
2.1递归算法
递归:直接或间接调用自身的算法。
递归函数:如 N!,Fibonacci,Ackerman函数
递归数据结构:单链表,二叉树,图等。
递归时,问题的本质没有发生变化,变化的是问题的规模。
递归的分类:
(1)直接调用自身
A()
{A()}
(2)间接调用自身
 B()
 {C()}
递归的优缺点:
(1)优点:算法简明,正确性易证明,是分析、设计的有利工具。
(2)缺点:执行效率不高,堆栈空间耗费。
原因:参数、局部量、返回点的保存。大量的重复计算。
 
例一:阶乘函数:
n!=1    n(n-1)!

int f(int n)
{
    sum=1;
    for(int i=1;i<=n;i++) sum=sum*i;
    return sum;
} 

int f(int n)
{
    if(n==0) return 1;
    return n*f(n-1); 
} 



递归算法的设计

对原问题f(s)进行分解,给出合理的较小问题f(s');
假设f(s')是可解的,给出f(s)与f(s')之间的关系。

确定一个特定情况的解,由此作为递归出口。
含一个递归调用的递归过程的一般形式 
递归定义包括两项:
*/

/*
栈的应用:递归
1、函数是递归定义的

阶乘函数
long Factorial(long n)
{
    if(n==0)return 1;
    else return n*Factorial(n-1);
} 

递归调用 回归求值 
2、数据结构是递归的--单链表
搜索链表最后一个结点并打印其数值

template<class T>
void Print(ListNode<T>*f)
{
    if(f->link==NULL)
     cour<<f->data<<endl;
    else Print(f->link);
} 

3、问题的解法是递归的

汉诺塔解法:
如果n=1,则将这一个盘子直接从A柱移到C柱上。
否则,执行以下三步:
①:用C柱做过渡,将A柱上的(n-1)个盘子移到B柱上。
②:将A柱上最后一个盘子直接移到C柱上
③:用A柱做过渡,将B柱上的(n-1)个盘子移到C柱上。
A(n)--> C--> B
A(1) B(N-1)  C
A  B(n-1) C(1)
B(n-1)-->A-->C(1)
B  A  C(n) 

递归:自顶向下,逐步分解的策略。 
*/ 

/*
递归过程改为非递归过程
递归过程简洁、易懂、易编 

计算斐波那契额数列。
Fib(n)=n   Fib(n-1)+Fib(n-2),
 
*/

/*
递归与回溯 

对一个包含有许多结点,且每个结点有多个分支的问题,可以先选择一个分支进行搜,
当搜素到某一结点,发现无法再继续搜索下去时,可以沿搜索路径回退
到前一结点,沿另一分支继续搜索。
如果回退之后没有其他选择,再沿搜索路径回退到更前结点。
依次执行,直到搜索到问题的解。
回溯算法是一种深度算法。 

*/ 

/*
3.迷宫问题(穷举法)
寻找一条从入口到出口的通路。

4、N皇后问题。
n个皇后不能在同一行,同一列,同一对角线上。 

*/ 
3

 

推荐阅读