数据结构学习笔记(二)

  
线性表的链式表示和实现
线性链表: 用一组任意的存储单元来依次存放线性表的结点,其存储单元可以是任意位置的.因此,链表种结点的逻辑次序和物理次序不一定相同. 每一个结点值及一个指针两部分组成了链表种的结点结构.
 Data
 link
其中,data域是数据域,用来存放结点值. Next是指针域,存放结点的直接后继的地址.
Typedef    char datatype;
Typedef struct node{
        datatype data;
        struct   node *next;
}listnode;
typedef listnode *linklist;
listnode *p;
linklist head;
注意区分指针变量和结点变量这两个不同的概念。
P为动态变量,它是通过标准函数生成的,即
       p=(listnode*)malloc(sizeof(listnode));
函数malloc分配了一个类型为listnode的结点变量的空间,并将其首地址放入指针变量p中。一旦p所指的结点变量不再需要了,又可通过标准函数
                   free(p)
释放所指的结点变量空间。
 
头插法建表:
linklist createlistf(void)
 {
      char ch;
      linklist head;
      listnode *p;
       head=null;
       ch=getchar( );
       while (ch!=‵/n′{
           p=(listnode*)malloc(sizeof(listnode));
            p–>data=ch;
            p–>next=head;     
head=p;
       ch=getchar( );
      }
      return (head);
   }
 
listlink createlist(int n)
     {   int data;
          linklist head;
          listnode *p
          head=null;
         for(i=n;i>0;--i){       
             p=(listnode*)malloc(sizeof(listnode));
             scanf((〝%d〞,&p–>data);            
            p–>next=head;
             head=p;
            }
return(head);
   }
尾插法建表
头插法建立链表虽然算法简单,但生成的链表中结点的次序和输入的顺序相反。若希望二者次序一致,可采用尾插法建表。该方法是将新结点插入到当前链表的表尾上,为此必须增加一个尾指针r,使其始终指向当前链表的尾结点。例:
linklist creater(    )
 {
    char ch;
    linklist head;
    listnode *p,*r;        //(, *head;)
    head=NULL;r=NULL;
    while((ch=getchar( )!=‵/n′){
        p=(listnode *)malloc(sizeof(listnode));
        p–>data=ch;
        if(head=NULL)
              head=p;
        else   
r–>next=p;
             r=p;
        }
          if (r!=NULL)
              r–>next=NULL;
          return(head);
   }
 
无论链表是否为空,其头指针是指向头结点在的非空指针(空表中头结点的指针域为空),因此空表和非空表的处理也就统一了。
     其算法如下:
linklist createlistr1( ){
 char ch;
 linklist head=(linklist)malloc(sizeof(listnode));
    listnode *p,*r
r=head;
    while((ch=getchar( ))!=‵/n′{
           p=(listnode*)malloc(sizeof(listnode));
           p–>data=ch;
           p–>next=p;
           r=p;
         }
       r–>next=NULL;
       return(head);
   }
上述算法里动态申请新结点空间时未加错误处理,可作下列处理:
       p=(listnode*)malloc(sizeof(listnode))
       if(p==NULL)
         error(〝No space for node can be obtained〞);
          return ERROR;
         以上算法的时间复杂度均为O(n)。
 
查找算法
在链表中,即使知道被访问结点的序号i,也不能象顺序表中那样直接按序号i访问结点,而只能从链表的头指针出发,顺链域next逐个结点往下搜索,直到搜索到第i个结点为止。因此,链表不是随机存取结构。
     设单链表的长度为n,要查找表中第i个结点,仅当1≦i≦n时,i的值是合法的。但有时需要找头结点的位置,故我们将头结点看做是第0 个结点,其算法如下:
Listnode * getnode(linklist head , int i)
   {
     int j;
     listnode * p;
     p=head;j=0;
     while(p–>next && j<I){
           p=p–>next;
            j++;
          }
      if (i==j)
          return p;
else
         return NULL;
   }
按值查找
    按值查找是在链表中,查找是否有结点值等于给定值key的结点,若有的话,则返回首次找到的其值为key的结点的存储位置;否则返回NULL。查找过程从开始结点出发,顺着链表逐个将结点的值和给定值key作比较。其算法如下:
Listnode * locatenode(linklist head,int key)
 {
    listnode * p=head–>next;
    while( p && p–>data!=key)
        p=p–>next;
        return p;
    }
插入运算:
具体算法如下:
    void insertnode(linklist head,datetype x,int i)
    {
       listnode * p,*q;
      p=getnode(head,i-1);
      if(p==NULL)
           error(〝position error〞);
        q=(listnode *)malloc(sizeof(listnode));
        q–>data=x;
        q–>next=p–next;
         p–>next=q;
      }
设链表的长度为n,合法的插入位置是1≦i≦n+1。注意当i=1时,getnode找到的是头结点,当
 i=n+1时,getnode找到的是结点an。因此,用i-1做实参调用getnode时可完成插入位置的合法性检查。
删除运算
 删除运算是将表的第i个结点删去。因为在单链表中结点ai的存储地址是在其直接前趋结点a a i-1的指针域next中,所以我们必须首先找到
a i-1的存储位置p。然后令p–>next指向ai的直接后继结点,即把ai从链上摘下。最后释放结点ai的空间,将其归还给“存储池”。此过程为:
具体算法如下:
 void deletelist(linklist head, int i)
   {
     listnode * p, *r;
     p=getnode(head,i-1);
      if(p= =NULL || p–>next= =NULL)
           return ERROR;
      r=p–>next;
      p–>next=r–>next;
       free( r ) ;
      }
设单链表的长度为n,则删去第i个结点仅当1≦i≦n时是合法的。注意,当i=n+1时,虽然被删结点不存在,但其前趋结点却存在,它是终端结点。因此被删结点的直接前趋*p存在并不意味着被删结点就一定存在,仅当*p存在(即p!=NULL)且*p不是终端结点
 (即p–>next!=NULL)时,才能确定被删结点存在。
循环链表
     循环链表时一种头尾相接的链表。其特点是无须增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活。
单循环链表:在单链表中,将终端结点的指针域NULL改为指向表头结点的或开始结点,就得到了单链形式的循环链表,并简单称为单循环链表。
例、在链表上实现将两个线性表(a1,a2,a3,…an)和(b1,b2,b3,…bn)链接成一个线性表的运算。
 linklist connect(linklist heada,linklist headb)
   {
       linklist p=heada—>next;
       heada—>next=(headb—next)—>next
       free(headb—>next);
       headb—>next=p;
       return(headb);
       }
和单链表类似,双链表一般也是由头指针唯一确定的,增加头指针也能使双链表上的某些运算变得方便,将头结点和尾结点链接起来也能构成循环链表,并称之为双向链表。
       设指针p指向某一结点,则双向链表结构的对称性可用下式描述:
     (p—>prior)—>next=p=(p—>next)—>prior
    即结点*p的存储位置既存放在其前趋结点*(p—>prior)的直接后继指针域中,也存放 在它的后继结点*(p—>next)的直接前趋指针域中。
双向链表的前插操作算法如下:
   void dinsertbefor(dlistnode *p,datatype x)
     {
      dlistnode *q=malloc(sizeof(dlistnode));
      q—>data=x;
       q—>prior=p—>prior;
       q—>next=p;
       p—>prior—>next=q;
       p—>prior=q;
       }
void ddeletenode(dlistnode *p)
     {
       p–>prior–>next=p–>next;
       p–>next–>prior=p–>prior;
       free(p);
      }
 注意:与单链表的插入和删除操作不同的是,在双链表中插入和删除必须同时修改两个方向上的指针。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值