线性表

Posted 阿呆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线性表相关的知识,希望对你有一定的参考价值。

线性表是由 \\(n\\)个元素\\((n \\ge 0)\\)组成的有限序列,线性表的特征

  • 所有数据元素类型相同
  • 线性表是由有限个元素构成
  • 线性表中的数据元素是与位置是有关的,这一点表明线性表不同于集合,线性表中每个元素都有一个对应的序号,线性表中的元素可以重复出现。

线性表的逻辑结构一般表示为:\\( a_1,a_2,...,a_{i-1},a_i,a_{i+1},...,a_n \\)

  • 除起始元素没有\\(a_1\\) 没有前驱元素之外,其他元素\\(a_i\\)有且仅有一个前驱元素\\(a_{i-1},\\)
  • 除终端元素没有后继元素外,其他元素 \\(a_i\\) 有且仅有一个后继元素\\(a_{i+1}\\)

线性表是一种逻辑结构,线性表的存储结构有

  • 顺序存储
  • 链式存储

顺序表

顺序表的特点

  • 属于直接映射(逻辑上相邻的元素,其物理位置也相邻),
  • 具有随机存取特性( 通过首地址和元素序号可以在 \\(O(1)\\) 时间内找到指定元素 )
  • 删除和插入元素需要移动大量元素。
    • 插入元素移动的平均次数\\(=\\frac{1}{n}\\sum_{i=1}^{n}(n-i+1)=\\frac{n}{2}\\), 因此时间复杂度为\\(O(n)\\)
    • 删除元素移动的平均次数\\(=\\frac{1}{n}\\sum_{i=1}^{n}(n-i)=\\frac{n-1}{2}\\),因此时间复杂度为 \\(O(n)\\)
  • 存储密度高,其值为\\(1\\)。\\(存储密度=节点数据本身所占用的存储量/节点结构占用的存储量\\)

描述顺序结构的三个属性

  • 顺序表的起始地址
  • 顺序表的最大长度
  • 顺序表的当前长度
# define MaxSize 100
typedef struct
   {
       int data[MaxSize];
       int length;
   } SqList;

给定一个有 \\(n\\) 个元素的整型数组\\(arr\\),其中连续的相等元素构成的子序列称为平台,设计一个算法求 \\(arr\\)中最长平台的长度。

int maxLength(int arr[], int n)
{
    int len = 1;
    int max = 1;
    int start = 0;

    for (int i = 1; i < n; i++)
    {
        if (arr[i] == arr[start])
            len++;
        else
        {
            if (len > max)
                max = len;

            start = i;
            len = 1;
        }
    }

    if (len > max)
        max = len;

    return max;
}

设有一个顺序表\\(L\\), 其元素为整型数据,设计一个算法将\\(L\\)中所有小于\\(0\\)的整数放在前半部分,大于等于\\(0\\)的整数放在后半部分。

void change(SqList &L)
{
    int tmp;
    int i = 0, j = L.length - 1;
    while (i < j)
    {
        while (i < j && L.data[i] < 0)
            i++;
        while (i < j && L.data[j] >= 0)
            j--;
        if (i < j)
        {
            tmp = L.data[i];
            L.data[i] = L.data[j];
            L.data[j] = tmp;
        }
    }
}

设有一个顺序表\\(L\\),其元素为整型。设计一个尽可能高效的算法,以第一个元素为分界线,将所有小于等于它的元素移到该元素前面,将所有大于它的元素移到该元素的后面。

void move(SqList &L)
{
    int i = 0;
    j = L.length - 1;
    int pivot = L.data[0];
    int tmp;

    while (i < j)
    {
        while (i < j && L.data[j] > pivot)
            j--;
        while (i < j && L.data[i] <= pivot)
            i++;

        tmp = L.data[i];
        L.data[i] = L.data[j];
        L.data[j] = tmp;

    }

    if (L.data[i] < pivot)
    {
        tmp = L.data[0];
        L.data[0] = L.data[i];
        L.data[i] = tmp;
    }
}

void move(SqList &L)
{
    int i = 0, j = L.length - 1;
    int pivot = L.data[0];

    while (i < j)
    {
        while (i < j && L.data[i] > pivot)
            j--;
        L.data[i] = L.data[j];
        i++;
        while (i < j && L.data[i] <= pivot)
            i++;
        L.data[j] = L.data[i];
        j--;
    }

    L.data[i] = pivot;
}


void move(SqList &L)
{
    int i = 0;
    int pivot = L.data[0];
    int tmp;

    for (int j = 1; j < L.length; j++)
    {
        i++;
        if (i != j)
        {
            tmp = L.data[i];
            L.data[i] = L.data[j];
            L.data[j] = tmp;
        }
    }
    tmp = L.data[0];
    L.data[0] = L.data[i];
    L.data[i] = tmp;
}

已知长度为 \\(n\\) 的线性表 \\(L\\) 采用顺序存储结构,编写一个时间复杂度为\\(O(n)\\),空间复杂度为\\(O(1)\\) 的算法,该算法删除线性表中所有值为\\(x\\)的数据元素。

void deleteSameElements(SqList &L, int x)
{
    int base = 0;

    for (int i = 0; i < L.length; i++)
    {
        if (L.data[i] != x)
        {
            L.data[base] = L.data[i];
            base++;
        }
    }
    L.length = base;
}

void deleteSameElements(SqList &L, int x)
{
    int count = 0;
    int i = 0;

    while (i < L.length)
    {
        if (L.data[i] == x)
            count++;
        else
            L.data[i - count] = L.data[i];
        i++;
    }
    L.length = L.length - count;
}

已知长度为 \\(n\\) 的线性表 \\(L\\) 采用顺序存储结构,试设计一个时间复杂度空间复杂度两方面都尽可能高效的算法,该算法删除线性表中元素值为\\([x,y]\\)之间的所有数据元素。

单链表

单链表是用任意一组存储单元存放线性表中的元素,每个节点通过一个指针指向后继节点。这组存储单元可以是连续的也可以是不连续的。

  • 通过头节点(带头节点的单链表)或首节点(不带头节点的单链表)的指针来标识一个链表
  • 从一个已知节点出发,只能访问该节点和通过\\(next\\)指针访问其后继节点,无法直接找到该节点之前的其他节点
  • 在单链表中插入一个节点或者删除一个节点必须已知其前驱节点,插入和删除操作不需要移动节点
typedef struct LinkedNode
{
  T data;  //T表示数据类型
  struct LNode* next;
}LinkedList;

建立单链表的两种方式

  • 头插法「逆序了」

    void creatLinkedList(LinkedList* &L, int a[],int n)
    {
     LinkedList* s;
     L=(LinkedList*)malloc(sizeof(LinkedList));
     L->next=NULL;
    
     for(int i=0;i<n;i++)
     {
       s=(LinkedList*)malloc(sizeof(LinkedList));
       s->data=a[i];
       s->next=NULL;
       L->next=s;
     }
    }
    
  • 尾插法「还是原来的顺序」

    void creatLinkedList(LinkedList* &L, int a[],int n)
    {
     LinkedList* s,*r;
     L=(LinkedList*)malloc(sizeof(LinkedList));
     r=L; //r始终指向终端节点,开始时指向头结点
    
     for(int i=0;i<n;i++)
     {
       s=(LinkedList*)malloc(sizeof(LinkedList));
       s->data=a[i];
       r->next=s;
       r=s;
     }
     r->next=NULL;
    }
    

单链表的基本操作

  • 按序号查找节点值算法

    int findNode(LinkedList *L,int i,int &e)
    {
    int j=0;
    LinkedList *ptr=L;
    
    while(ptr!=NULL && j<i)
    {
      j++;
      ptr=ptr->next;
    }
    if(NULL==ptr) return 0;
    else
    {
     e=ptr->data;
     return 1;
    }
    }
    
  • 按元素值查找序号算法

    int findNode(LinkedList *L,int key)
    {
    LinkedList *ptr=L->next;
    int index=0;
    
    while(ptr!=NULL && ptr->data!=key)
    {
      index++;
      ptr=ptr->next;
    }
    
    if(ptr!=NULL) return index;
    else return 0;
    }
    
  • 插入元素:将值为\\(x\\)的元素的新节点插入到第\\(i\\)个节点的位置上,即先在单链表中找到插入节点的前驱节点,即第\\(i-1\\)个节点,再在其后插入新节点。

    s->next=p->next;
    p->next=s;
    
  • 删除元素:将单链表中的第 \\(i\\) 个节点删除。

    q=p->next;
    p->next=q->next;
    free (q);
    

有一个线性表\\((a_1,a_2,...,a_n)\\) 采用带头节点的单链表\\(L\\)存储,设计一个就地算法将其就地逆置,所谓“就地”是指算法的辅助空间为\\(O(1)\\).

void reverse(LinkedList * & L)
{
  LinkedList *ptr=L->next;
  LinkedList *q;
  L->next=NULL;
  while(ptr!=NULL)
  {
    q=ptr->next;
    ptr->next=L->next;
    L->next=ptr;
    ptr=q;
  }
}

设\\(C=\\{a_1,b_1,a_2,b_2,...,a_n,b_n\\}\\) 为一线性表,采用带头节点的\\(hc\\)单链表存放,设计一个就地算法,将其拆分为两个线性表(它们都是用单链表存放)使得\\(A=\\{a_1,a_2,...,a_n\\},B=\\{b_1,b_2,...,b_n\\}\\)

void split(LinkedList *hc,LinkedList *&ha,LinkedList *&hb)
{
  LinkedList *r=ha;
  LinkedList *p=hc->next;
  LinkedList *q;
  
  hb->next=NULL;
  while(p!=NULL)
  {
    r->next=p;
    r=p;
    
    p=p->next;
    
    q=p->next;
    p->next=hb->next;
    hb->next=p;
    p=q;
  }
  r->next=NULL;  //别忘了
}

王道题讲解

有一个带头结点的单链表\\(L\\),设计一个算法使其元素递增有序。

void sort(LinkedList * &L)
{
  LinkedList *p=L->next;
  LinkedList *q=p->next;
  LinkedList *pre,tmp;
  
  p->next=NULL:   //含有一个元素的有序单链表
  p=q;
  
  while(p!=NULL)
  {
    q=p->next;
    pre=L;   //单链表只能从头开始往后寻找节点
    
    tmp=pre->next;
    while(tmp!=NULL && tmp.data < p->data)
    {
      pre=tmp;
      tmp=tmp->next;
    }
    
    p->next=pre->next;
    pre->next=p;
    
    p=q;
  }
}

给定两个单链表,编写算法找出其公共的节点
分析:从头到尾扫描单链表\\(A\\),判断当前元素是否在单链表\\(B\\)中出现,若在则插入到单链表\\(C\\)中。

void findSameNode(LinkedList *A,LinkedList *B,LinkedList *&C)
{
  LNode *p=A->next;
  LNode *q=B->next;
  LNode *r;
  
  C=(LNode*)malloc(sizeof(LNode));
  C->next=NULL;
  r=C;
  while(p!=NULL)
  {
    q=B->next;
    while(q!=NULL && q.data!=p.data)
         q=q->next;
    
    if(q!=NULL)
    {
      r->next=p;
      r=p;
    }
    p=p->next;
  }
}

\\(2010\\)年联考真题
设将\\(n\\) 个整数存放到一维数组R中。试设计一个在时间和空间尽可能高的算法。将\\(R\\)中保存的序列循环左移\\(p (0 \\lt q\\lt n)\\)个位置,即将\\(R\\)中的数据由\\((X_0,X_1,...,X_{n-1})\\) 变换为\\((X_p,X_p+1,...,X_{n-1},X_0,X_1,....,X_{p-1})\\)

void reverse(int R[],int left,int right)
{
 int i=left,j=right;
 int tmp;
 while(i<j)
 {
   tmp=R[i];
   R[i]=R[j];
   R[j]=tmp;
   i++;
   j--;
 }
}

void leftShift(int R[],int n,int p)
{
  if(p>0 &&p<n)
  {
   reverse(R,0,n-1);
   reverse(R,0,n-p-1);
   reverse(R,n-p,n-1);
  }
}

\\(2011\\)年联考真题
给定两个数组\\(A\\)和\\(B\\),数组的长度为\\(n\\),两个数组都分别有序,求出两个数组中的所有数的中位数。

int search(int A[],int B[],int n)
{
  int i,j,k;
  i=j=k=0;
  while(i<n &&j<n)
  {
    k++;
    if(A[i]<B[j])
    {
      if(k==n)
        return A[i];
       i++;
    }
    else
    {
      if(k==n)
        return B[j];
      j++;
    }
  }
}

分别求出两个升序序列\\(A\\),\\(B\\)的中位数,记为\\(a\\),\\(b\\)。若\\(a=b\\),则\\(a\\)或\\(b\\)即为所求,否则舍弃\\(a,b\\)中较小者所在序列的较小一半,同时舍弃较大者所在序列的较大一半,要求两次舍弃的元素个数相同(每次从左侧和右侧删除相同个数的元素后,新的两个数组,它们的中位数与原始数组的中位数是相同的)。重复上述过程,直到两个序列均只含一个元素为止,则较小的即为所求的中位数

int searchMid(int A[],int B[],int n)
{
  int startA,midA,endA;
  int startA,midB,endB;
  
  startA=0; endA=n-1; 
  startB=0; endB=n-1;
  
  while(startA!=endA || startB!=endB)
  {
     midA=(startA+endA)/2;
     midB=(startB+endB)/2;
     
     if(A[midA]==B[midB])
        return A[midA];
        
     if(A[midA]<B[midB])
     {
       if((startA+endA)%2==0)//若元素个数为奇数时
       {
         startA=midA;  //舍弃A中间点以前的部分且保留中间点
         endB=midB;    //舍弃B中间点以后的部分且保留中间点
       }
       else  //若元素个数为偶数时
       {
        startA=midA+1;  //舍弃A的前半部分,每次舍弃的长度相同,可以保证同时到达
        endB=midB;      //舍弃B的后半部分
       }
     }
     else if(A[midA] > B[midB])
     {
       if((startA+endA)%2==0)//若元素个数为奇数时
       {
         endA=midA;  //舍弃A中间点以后的部分且保留中间点
         startB=midB;//舍弃B中间点以前的部分且保留中间点
       }
       else  //若元素个数为偶数时
       {
        endA=midA;       //舍弃A的后半部分
        startB=midB+1;   //舍弃B的前半部分
       }
     }
  }
  
  return A[startA]<B[startB]?A[startA]:B[startB];
}
  • 若\\(A\\)和\\(B\\)数组的长度为\\(2k+1\\),取\\(A\\)数组的中位数为\\(A[k]\\),\\(B\\)数组的中位数为\\(B[k]\\),\\(A\\)和\\(B\\)组合起来的中位数应该是第\\(2k+1\\)大的那个数。如果\\(A[k]==B[k]\\),则\\(A[k]\\)必定为第\\(2k+1\\)大的数,是所有数字的中位数。如果\\(A[k]\\gt B[k]\\),则\\(A[k]\\)至少为第\\(2k+2\\)大的数,\\(B[k]\\)至多为第\\(2k+1\\)大的数,中位数介于\\(B[k]\\)和\\(A[k]\\)之间。
  • 若\\(A\\)和\\(B\\)数组的长度为\\(2k\\),按照题目所述条件,则\\(A\\)的中位数为\\(A[k-1]\\),则\\(B\\)的中位数为\\(B[k-1]\\),\\(A\\)和\\(B\\)组合起来的中位数应该是第\\(2k\\)大的那个数,若\\(A[k-1]==B[k-1]\\),则\\(B[k-1]\\)必为第\\(2k\\)大的那个数,即所有数字的中位数。如果\\(A[k-1]\\gt B[k-1]\\),则\\(A[k-1]\\)至少为第\\(2k\\)大的数,\\(B[k-1]\\)至多为第\\(2k-1\\)大的数,中位数介于\\(B[k-1]\\)和\\(A[k-1]\\)之间。

\\(2013\\)年联考真题
chrome_2016-06-26_16-46-33.png
主元素问题方法一:对数组中元素进行计数,然后查看出现次数最多的元素,若次数大于一半,则为主元素。这种方式只需要对数组扫描一遍,时间复杂度为\\(O(n)\\),空间复杂度为\\(O(n)\\)。

int mainElement(int A[],int n)
{
  int *p=(int*)malloc(sizeof(int)*n);
  int max=0;
  
  for(int i=0;i<n;i++) p[i]=0;
  
  for(int i=0;i<n;i++)
  {
    p[A[i]]++;
    if(p[A[i])>p[A[max]]) max=A[i];
  }
  
  if(p[max]>(n/2)  return max;
  else return -1;
}

使用快速排序,然后统计相同元素出现的最大次数

void quickSort(int A[],int left,int right)
{
 int i=left,j=right;
 int tmp;
 
 if(i<j)
 {
    tmp=A[i];
    while(i<j)
    {
      while(i<j && A[j]>tmp) j--;
      A[i]=A[j];
      i++;
      while(i<j && A[i]<=tmp) i++;
      A[j]=A[i];
      j--;
    }
    A[i]=tmp;
    quickSort(A,left,i-1);
    quickSort(A,i+1,right);
 }
}
//然后使用平台算法

数组中存在主元素时,所有的非主元素个数和必少于一半。如果让主元素与一个非主元素"配对“,则最后多出来的元素(没有元素与之配对)就是主元素。从前往后扫描数组元素,假定遇到的当前值选定为主元素,再次遇到它时计数加1,遇到不等的值时,计数减1。当计数减为0后,将遇到的下一个值重新选定为主元素。扫描完毕,当前选定的元素(计数值大于0)可能是主元素,但未必是主元素。还需要对数组再进行一次扫描,记录它出现的实际个数,以判定它是否是主元素。

void mainElemment(int A[],int n)
{
 int base=A[0];
 int count=1;
 int count2=0;
 for(int i=1;i<n;i++)
 {
   if(A[i]==base)
      count++;
   else 
   {  
       if(count>0) count--;
       else 
       {
        base=A[i];
        count=1;
       }
    }
 }
 
 if(count>0)
 {
    count2=0;
    for(int i=0;i<n;i++)
    {
      if(base==A[i])
        count2++;
    }
 }
 if(count2>(n/2))  return base;
 else return -1;
}

以上是关于线性表的主要内容,如果未能解决你的问题,请参考以下文章

如何在android中的地图片段内中心线性布局?

垂直线性布局中的多个片段

线性表的插入和删除操作代码(C语言)

在android中的类内的对话框片段的线性布局中添加textview

数据结构学习笔记二线性表---顺序表篇(画图详解+代码实现)

数据结构学习笔记二线性表---顺序表篇(画图详解+代码实现)