【数据结构】之 线性表详解

线性表(Linear List)

基本概念

线性表是由n(n>=0)个类型相同数据元素组成的有限序列数据元素可由若干个数据对象组成,且一个线性表中的数据元素必须属于同一数据对象

线性表示n个类型相同数据元素的有限序列,对n>0,除第一个元素无直接前驱,最后一个元素无直接后继外,其余的每个数据元素只有一个直接前驱和直接后继。

线性表的逻辑结构如图:

线性表具有如下特点:

同一性:线性表由同类数据元素组成,每个元素必须属于同一数据类型

有穷性:线性表由有限个数据元素组成,表长度就是表中数据元素的个数。

线性表中相邻数据元素之间存在着序偶关系。

   

线性表的顺序存储

线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表的各个元素,使得线性表中在逻辑结构上相邻的数据元素存储在连续的物理存储单元中,即通过数据元素物理存储的连续性来反映数据元素逻辑上的相邻关系。

采用顺序存储结构存储的线性表通常简称为顺序表。可将顺序表归纳为:关系线性化,结点顺序化。

顺序存储表示

定义

用c语言定义线性表的顺序存储结构:

typedef struct{
    char data[MAXSIZE];    
    int len;    //数据长度 
}List;

那么线性表的存储地址是如何计算的呢?

假设线性表中有n个元素,每个元素占 sizeof(ElemType)个单元,则第一个元素的地址为 LOC(A) ,则第n个元素的地址为: LOC(n) = LOC(A) + (n-1)* sizeof(ElemType) 。

顺序表的存储结构如下:

   

我们只是定义了顺序表,还没有创建呢!

创建

//初始化顺序表 
List* initList(List *L){
    int num,i;
    char ch;
    //输入顺序表长度
    printf("请输入顺序表长度(0<len<100): ");
    scanf("%d",&num);
    L->len = num;
    
    //输入数据 
    for(i = 0; i < L->len; i++){
        getchar();
        printf("请输入第 %d 个数:",i);
        scanf("%c",&ch);
        L->data[i] = ch;
    }
    return L;
}

基本操作

线性表的基本操作有查找,插入,删除。

查找

查找分为两种:1.可以按序号查找 getAsNum(L,i) :查找线性表L中第i个数据元素。2.按内容查找 getAsContent(L,content) :查找顺序表L中的和Content相等的数据元素

【算法思想】:按内容查找运算可采用顺序查找,即从第一个元素开始,依次将表中元素content相比较,若想等,则查找成功,返回该元素在表中的序号;若都不想等,则查找失败,返回-1

【算法描述】按内容查找算法

//按内容查找
int getAsContent(List L,char content){
    unsigned int i = 0; 
    while(i >= 0 || i <= L.len - 1){
        //顺序扫描表,找到对应元素,或者扫描完则退出 
        if(L.data[i] != content){
            i++;
        }else{ 
            break;
        }
    }
    if(i <= L.len - 1){
        //找到则输出并返回序号 
        printf("按内容查找成功,第 %d 个位置元素为 %c \n\n",i,L.data[i]);
        return i;
    }else{
        //未找到 
        printf("查找失败,没有你所找的元素!\n\n");
        return ERROR; 
    }
}

小结:查找时要注意判断表中没有你要查找的元素的情况,此算法时间复杂度为O(n)。

插入

插入操作指在第i个元素之前插入一个新的元素,这时线性表的长度就变成了n+1了。

【算法思想】:用顺序表作为线性表的存储结构时,由于结点的物理顺序必须和结点的逻辑顺序保持一致,因此必须将原表位置的n,n-1,……,i上的结点依次后移一个位置(此时原表移动对应得位置为n+1,n,……,i+1),空出第i个位置,然后在该位置插入新结点。注意插入的位置为表尾时的情况。

【算法描述】顺序表的插入运算

int insertList(List *L,int i,char content){
    int k;
    
    //插入的位置不在表的范围 
    if(i < 0 || i >= L->len){
        printf("插入位置不合法!\n\n");
        return ERROR;
    }
    
    //考虑表满的情况 
    if(L->len == MAXSIZE){
        printf("表已满!无法插入!\n\n");
        return ERROR;
    }else if(i >= 0 && i <= L->len - 1){
        
        //插入位置后的元素向后移动 
        for(k = L->len - 1; k >= i; k--){
            L->data[k+1] = L->data[k];
        }
        L->data[i] = content;
        //表长度加一 
        L->len++;
        printf("插入成功!\n\n");
        print(L);
        return OK;
    }
}  

小结:设E为在长度为n的表中插入一元素所需移动元素的平均次数,假设为在第i个元素之前插入元素的概率,并假设在任何位置上插入的概率相等,即,i = 1,2,……,n+1,则有:

 

删除

线性表的删除操作指的是将表的第i个(1<=i<=n)个元素删去,使长度为n的线性表变成长度为n-1的线性表

【算法思想】:类似于插入操作,用顺序表作为线性表的存储结构时,由于结点的物理顺序必须和结点的逻辑顺序保持一致,空出第i个位置就要将原表位置的上的结点依次前移一个位置。

【算法描述】顺序表的删除运算

//删除 
int deleteList(List *L,char *content){
    int k;
    
    //删除位置不合法则推出 
    if(i < 0 || (i >= L->len)){
        printf("删除位置不合法!\n\n");
        return ERROR;
    }
    
    //删除的表已空 
    if(L->len == 0){
        printf("表已空!\n\n");
        return ERROR;
    }else{
        *content = L->data[i];
        
        //前移 
        for(k = i; k <= L->len - 2; k++){
            L->data[k] = L->data[k+1];
        }
        
        //删除成功后表长度减一 
        L->len--;
        printf("删除成功!\n\n");
        print(L);
        return OK;
    }
} 

小结:与插入算法类似,删除运算也要移动结点。设E为删除一个元素所需移动元素的平均移动次数, 为 删除第i个元素的概率,并假设在任何位置上删除的概率相等,即 ,i=1,2,……,n 。则有:

最后的汇总代码如下:

/*
线性表的顺序存储 
基本操作:查找,插入,删除 
*/

#include<stdio.h>
#include<string.h>

#define MAXSIZE 100             
#define OK 1
#define ERROR -1

typedef struct{
    char data[MAXSIZE];    
    int len;    //数据长度 
}List;

List* initList(List *L);                                                    //初始化 
int getAsNum(List L,int num);                                        //按序号查找 
int getAsContent(List L,char content);                        //按内容查找
int insertList(List *L,char content);            //插入,在元素之前插入        
int deleteList(List *L,char *content);        //删除
void print(List *L);                                                            //打印

//初始化顺序表 
List* initList(List *L){
    int num,&ch);
        L->data[i] = ch;
    }
    return L;
}

//按序号查找 
int getAsNum(List L,int num){
    if(num < 0 || num > L.len - 1){
        printf("查找失败,位置 %d 超过链表长度!\n\n",num);
        return ERROR;
    }else{
        printf("按序号查找成功,第 %d 个位置元素为 %c \n\n",num,L.data[num]);
        return num;
    }
}

//按内容查找
int getAsContent(List L,L.data[i]);
        return i;
    }else{
        //未找到 
        printf("查找失败,没有你所找的元素!\n\n");
        return ERROR; 
    }
}

//插入,在元素之前插入
int insertList(List *L,char content){
    int k;
    
    //插入的位置不在表的范围 
    if(i < 0 || i >= L->len){
        printf("插入位置不合法!\n\n");
        return ERROR;
    }
    
    //考虑表满的情况 
    if(L->len == MAXSIZE){
        printf("表已满!无法插入!\n\n");
        return ERROR;
    }else if(i >= 0 && i <= L->len - 1){
        
        //插入位置后的元素向后移动 
        for(k = L->len - 1; k >= i; k--){
            L->data[k+1] = L->data[k];
        }
        L->data[i] = content;
        //表长度加一 
        L->len++;
        printf("插入成功!\n\n");
        print(L);
        return OK;
    }
} 

//删除 
int deleteList(List *L,char *content){
    int k;
    
    //删除位置不合法则推出 
    if(i < 0 || (i >= L->len)){
        printf("删除位置不合法!\n\n");
        return ERROR;
    }
    
    //删除的表已空 
    if(L->len == 0){
        printf("表已空!\n\n");
        return ERROR;
    }else{
        *content = L->data[i];
        
        //前移 
        for(k = i; k <= L->len - 2; k++){
            L->data[k] = L->data[k+1];
        }
        
        //删除成功后表长度减一 
        L->len--;
        printf("删除成功!\n\n");
        print(L);
        return OK;
    }
}

//打印 
void print(List *L){
    int i; 
    printf("===================顺序表如下===================\n");
    printf("共有 %d 个数据: ",L->len); 
    for(i = 0; i < L->len; i++){
        printf("%c ",L->data[i]);
    }
    printf("\n");
}

int main(void){
    List L;
    int i,length,flag = 1;
    char ch,cha; 
    
    //初始化 
    initList(&L); 
    print(&L);
    
    //按序号查找 
    printf("请输入你要查找的元素序号:");
    scanf("%d",&num);
    getchar();
    getAsNum(L,num);
    
    //按内容查找 
    printf("请输入你要查找的元素的内容:");
    scanf("%c",&ch);
    getchar();
    getAsContent(L,ch);
    
    //插入元素 
    printf("请输入你要插入的内容(格式:addr_num data_char):");
    scanf("%d %c",&num,&ch);
    getchar();
    insertList(&L,ch);
    
    //删除元素 
    printf("请输入你要删除的位置(格式:addr_num):");
    scanf("%d",&num);
    getchar();
    deleteList(&L,&cha);    
    
    return 0;
} 
顺序表

 

执行结果:

  

线性表的链式存储

链表:链表使用一组任意的存储单元来存放线性表的结点,这组存储单元可以是连续的,也可以是非连续的,甚至是零散分布在内存的任何位置。

采用链式存储结构的线性表称为线性链表。从链接方式看,链表可分为单链表,循环链表,双链表(也叫循环双链表,双向链表)。从实现角度看可分为动态链表和静态链表。

结点:结点包括两个域:数据域和指针域。数据域存放数据,指针域指向其他结点的地址。两个域的数量视具体情况而定。

单链表和循环单链表有一个指针域,双链表有连个指针域。

单链表

单链表中每个结点的存储地址存放在其前驱结点的指针域中,由于线性表的第一个结点无前驱,通常设一个头指针header指向第一个结点。

定义:

单链表的存储结构如下:

typedef struct Node{
    char ch;
    int len;    //表长度 
    struct Node *next;
}Node,*linkList;

 

创建

单链表的创建有两种:头插法和尾插法

代码在这里:

//尾插法建立表 
linkList createFromTail(linkList L){
    Node *s,*r;    //r指针始终指向链表尾部 
    char ch;
    int flag = 1;
    r = L;
    printf("尾插法建立表,请输入数据并以'#'结束输入:\n");
    while(flag){
        printf("输入数据: ");
        scanf("%c",&ch);
        getchar();
        if(ch == '#'){
            //若输入 # 则退出 
            flag = 0;
            r->next = NULL;
        }else{
            s = (linkList)malloc(sizeof(Node));
            s->ch = ch;
            r->next = s;
            r = s;
            (L->len)++; 
            flag = 1;
        }
    } 
    print(L);
    return L;
} 

  

基本操作

其基本操作和顺序表一样:查找,插入,删除。

查找

这里也只讲按值查找

【算法思想】:从单链表的头指针指向的头结点出发,顺链逐个将结点值和给定值作比较。

【算法描述】

//按内容查找
linkList getAsContent(linkList L){
    Node *p;
    char ch;
    int i = 1; 
    
    p = L->next;
    printf("\n请输入查找内容:");
    scanf("%c",&ch);
    getchar();
    
    //遍历完表且未找到数据退出循环, 找到数据时退出函数 
    while(p != NULL){
        if(p->ch == ch){
            printf("按内容查找成功,第 %d 个位置的数据为 %c\n",p->ch);
            return p;
        }
        p = p->next;
        i++;
    }
    
    //未找到数据
    if(p == NULL){
        printf("按内容查找失败!未在表中找到数据!\n");
    }
} 

 

插入

【算法思想】:首先要找到插入位置i的前一个结点,并用指针pre指向它,然后申请新结点s,通过修改pre和s的指针域将新结点插入链表。

【算法描述】

//插入
linkList insertList(linkList L){
    Node *pre,*s;
    int k,i;
    char ch;
    pre = L;
    k = 0;
    printf("\n请输入你要插入的位置和内容(格式: address content):");
    scanf("%d %c",&i,&ch);
    getchar();
    
    //插入位置不可能为负 
    if(i <= 0){
        printf("插入失败!插入位置不合法!插入位置不能为负\n");
        return NULL;
    }
    
    ////遍历完表且未找到插入位置(此时i大于表的长度) 或 找到插入位置时退出函数 退出循环
    while(pre != NULL && k < i - 1){
        pre = pre->next;
        k++;
    }
    
    if(pre == NULL){
        
        // 未找到插入位置(此时i大于表的长度)
        printf("插入失败!插入位置不合法!插入位置超出表的长度\n");
        return NULL;
    }else{
        
        //找到插入位置并插入数据 
        s = (linkList)malloc(sizeof(Node));
        s->ch = ch;
        s->next = pre->next;
        pre->next = s;
        L->len++;
        printf("插入成功!");
        print(L);
        return L;
    }
} 

 

删除

【算法思想】:通过计数方式找到删除位置的前一个结点并用pre指针指向它,然后删除结点并释放空间。

【算法描述】

//删除
linkList delList(linkList L){
    Node *pre,*r;
    int k = 0,i;
    char *ch;
    pre = L;
    printf("请输入删除的数据的位置(格式:address):");
    scanf("%d",&i); 
    getchar();
    
    //删除的位置必须合法 
    if(i > L->len || i<= 0){
        printf("删除的位置超出了链表的长度!\n");
        return;
    }
    
    // 找到删除位置退出
    while(pre->next != NULL && k < i - 1){
        pre = pre->next;
        k++;
    }
    
    //删除操作 
    r = pre->next;
    pre->next = r->next;
    free(r);
    L->len--;
    printf("删除成功!\n"); 
    print(L);
    return L;
} 

  

这是最后的完整算法:

/*
线性表的链式存储 
基本操作:查找,插入,删除
*/ 
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct Node{
    char ch;
    int len;    //表长度 
    struct Node *next;
}Node,*linkList;

void initList(linkList *L);                            //初始化链表 
linkList cteateFromeHead(linkList L);        //头插法建立表
linkList createFromTail(linkList L);        //尾插法建立表 
linkList getAsNum(linkList L);                    //按序号查找 
linkList getAsContent(linkList L);            //按内容查找
linkList insertList(linkList L);                //插入
linkList delList(linkList L);                                //删除
linkList print(linkList L);                            //查看链表
 
//初始化链表 
void initList(linkList *L){
    (*L) = (linkList)malloc(sizeof(Node));
    (*L)->len = 0;
    (*L)->next = NULL;
}

//头插法建立表 
linkList cteateFromeHead(linkList L){
    Node *s;
    char ch;
    int flag = 1;
    printf("头插法建立表,请输入数据并以'#'结束输入:\n"); 
    while(flag){
        printf("输入数据: ");
        scanf("%c",&ch);
        getchar();
        if(ch == '#'){
            //若输入 # 则退出 
            flag = 0;
        }else{
            s = (linkList)malloc(sizeof(Node));    
            s->ch = ch; 
            s->next = L->next;
            L->next = s;
            (L->len)++; 
            flag = 1;
        }
    }
    print(L);
    return L;
}

//尾插法建立表 
linkList createFromTail(linkList L){
    Node *s,&ch);
        getchar();
        if(ch == '#'){
            //若输入 # 则退出 
            flag = 0;
            r->next = NULL;
        }else{
            s = (linkList)malloc(sizeof(Node));
            s->ch = ch;
            r->next = s;
            r = s;
            (L->len)++; 
            flag = 1;
        }
    } 
    print(L);
    return L;
} 

//按序号查找 
linkList getAsNum(linkList L){
    int i,j;
    Node *p;
    
    p = L; 
    j = 0; 
    printf("\n请输入查找的序号: ");
    scanf("%d",&i);
    getchar();
    
    //查找的序号不可能为负 
    if(i <= 0){
        printf("输入不合法! \n");
        return NULL;
    }
    
    //退出情况有两种:表遍历完毕没有找到数据 或 p指针指向目标结点 
    while((p->next != NULL) && (j < i)){
        p = p->next;
        j++;
    }
    
    if(j == i){
        //找到结点 
        printf("按序号查找成功,序号 %d 的数据是 %c \n",p->ch);
        return p;
    }else{
        //未找到 
        printf("按序号查找失败!未在表中找到数据!\n");
        return NULL;
    }
}

//按内容查找
linkList getAsContent(linkList L){
    Node *p;
    char ch;
    int i = 1; 
    
    p = L->next;
    printf("\n请输入查找内容:");
    scanf("%c",p->ch);
            return p;
        }
        p = p->next;
        i++;
    }
    
    //未找到数据
    if(p == NULL){
        printf("按内容查找失败!未在表中找到数据!\n");
    }
} 

//插入
linkList insertList(linkList L){
    Node *pre,&ch);
    getchar();
    
    //插入位置不可能为负 
    if(i <= 0){
        printf("插入失败!插入位置不合法!插入位置不能为负\n");
        return NULL;
    }
    
    ////遍历完表且未找到插入位置(此时i大于表的长度) 或 找到插入位置时退出函数 退出循环
    while(pre != NULL && k < i - 1){
        pre = pre->next;
        k++;
    }
    
    if(pre == NULL){
        
        // 未找到插入位置(此时i大于表的长度)
        printf("插入失败!插入位置不合法!插入位置超出表的长度\n");
        return NULL;
    }else{
        
        //找到插入位置并插入数据 
        s = (linkList)malloc(sizeof(Node));
        s->ch = ch;
        s->next = pre->next;
        pre->next = s;
        L->len++;
        printf("插入成功!");
        print(L);
        return L;
    }
} 

//删除
linkList delList(linkList L){
    Node *pre,&i); 
    getchar();
    
    //删除的位置必须合法 
    if(i > L->len || i<= 0){
        printf("删除的位置超出了链表的长度!\n");
        return;
    }
    
    // 找到删除位置退出
    while(pre->next != NULL && k < i - 1){
        pre = pre->next;
        k++;
    }
    
    //删除操作 
    r = pre->next;
    pre->next = r->next;
    free(r);
    L->len--;
    printf("删除成功!\n"); 
    print(L);
    return L;
} 

//查看链表 
linkList print(linkList L){
    Node *p;
    int i = 0; 
    p = L->next;
    printf("==============查看链表数据为==============\n");
    printf("共有 %d 个数据(注意序号是从头结点0开始,即第一个数据序号为 1)\n",L->len);
    for(; i < L->len; i++){
        printf("%c ",p->ch);
        p = p->next;
    } 
    printf("\n\n");
    return L;
}

int main(void){
    Node *L;
    initList(&L);
    
    //头插法 
//    cteateFromeHead(L);

    //尾插法 
    createFromTail(L);         
    getAsNum(L);                     
    getAsContent(L);            
    insertList(L);                
    delList(L);        
    return 0;
} 
单链表

 

运行结果如图:

 

循环单链表

定义

循环链表是一个首尾相接的链表。将单链表最后一个结点的指针域有NULL改为指向表头结点,就得到了单链形式的循环链表,并成为循环单链表。

对于循环单链表,若经常要在首尾两端进行操作,则可以设一个为指针在表尾(如下图中C)。

c语言定义如下:

typedef struct cNode{
    char data;
    int len;    //表长 
    struct cNode *next;
}Node,*cNode;

创建

和前面一样,这里只讲尾插法创建

 

//尾插法建立表 
cNode createFromTail(cNode L){
    Node *s,*r;
    int i = 0,flag = 1;
    char data;
    
    r = L;
    printf("尾插法建立表,&data);
        getchar();
        if(data != '#'){
            s = (cNode)malloc(sizeof(Node));
            s->data = data;
            s->next = r->next;
            r->next = s;
            r = s;
            L->len++;
        }else{
            printf("结束输入...\n");
            flag = 0;
        }
    }
    r->next = L;
    print(L);
    return L;
}

基本操作

查找
//按内容查找
cNode searchAsContent(cNode L){
    Node *p;
    char data;
    int i = 1; 
    
    p = L->next;
    printf("\n请输入查找内容:");
    scanf("%c",&data);
    getchar();
    
    //遍历完表且未找到数据退出循环, 找到数据时退出函数 
    while(p != L){
        if(p->data == data){
            printf("按内容查找成功,第 %d 个位置的数据为 %c\n",p->data);
            return p;
        }
        p = p->next;
        i++;
    }
    
    //未找到数据
    if(p == L){
        printf("按内容查找失败!未在表中找到数据!\n");
    }
}  

 

插入
//插入
cNode insertCNode(cNode L){
    Node *pre,*s;    
    int k,i;
    char data;
    pre = L->next;
    k = 1;
    printf("\n请输入你要插入的位置和内容(格式: address content):");
    scanf("%d %c",&data);
    getchar();
    
    //插入位置不可能为负 
    if(i <= 0){
        printf("插入失败!插入位置不合法!插入位置不能为负\n");
        return NULL;
    }
    
    ////遍历完表且未找到插入位置(此时i大于表的长度) 或 找到插入位置时退出函数 退出循环
    while(pre != L && k < i - 1){
        pre = pre->next;
        k++;
    }
    
    if(pre == L){
        
        // 未找到插入位置(此时i大于表的长度)
        printf("插入失败!插入位置不合法!插入位置超出表的长度\n");
        return NULL;
    }else{
        
        //找到插入位置并插入数据     ,注意:pre指向插入位置的前一个结点  
        s = (cNode)malloc(sizeof(Node));
        s->data = data;
        s->next = pre->next;
        pre->next = s;
        L->len++; 
        printf("插入成功!");
        print(L);
        return L;
    }
} 

 

删除
//删除 
cNode delList(cNode L){
    Node *pre,i;
    pre = L;
    printf("请输入删除的数据的位置(格式:address):");
    scanf("%d",&i); 
    getchar();
    
    //删除的位置必须合法 
    if(i > L->len || i<= 0){
        printf("删除的位置超出了链表的长度!\n");
        return;
    }
    
    // 找到删除位置退出
    while(pre->next != L && k < i - 1){
        pre = pre->next;
        k++;
    }
    
    //删除操作 
    r = pre->next;
    pre->next = r->next;
    free(r);
    L->len--;
    printf("删除成功!\n"); 
    print(L);
    return L;
} 

 

完整代码:

/*
循环单链表 
*/

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct cNode{
    char data;
    int len;    //表长 
    struct cNode *next;
}Node,*cNode;

cNode initCNode(cNode *L);                //初始化 
cNode createFromTail(cNode L);        //尾插法建立表
cNode searchAsNum(cNode L);                //按序号查找
cNode searchAsContent(cNode L);        //按内容查找
cNode insertCNode(cNode L);                //插入
cNode delList(cNode L);                        //删除 
cNode print(cNode L);                            //查看链表

//初始化 
cNode initCNode(cNode *L){
    (*L) = (cNode)malloc(sizeof(Node));
    (*L)->len = 0;
    (*L)->next = (*L);
    return (*L);
}

//尾插法建立表 
cNode createFromTail(cNode L){
    Node *s,&data);
        getchar();
        if(data != '#'){
            s = (cNode)malloc(sizeof(Node));
            s->data = data;
            s->next = r->next;
            r->next = s;
            r = s;
            L->len++;
        }else{
            printf("结束输入...\n");
            flag = 0;
        }
    }
    r->next = L;
    print(L);
    return L;
}

//按序号查找
cNode searchAsNum(cNode L){
    int i,j;
    Node *p;
    
    p = L->next; 
    j = 1; 
    printf("\n请输入查找的序号: ");
    scanf("%d",&i);
    getchar();
    
    //查找的序号不可能为负 
    if(i <= 0){
        printf("输入不合法! \n");
        return NULL;
    }
    
    //单循环链表的到表尾的判断条件是 p != L 
    while((p != L) && (j < i)){
        p = p->next;
        j++;
    }
    
    if(j == i){
        //找到结点 
        printf("按序号查找成功,p->data);
        return p;
    }else{
        //未找到 
        printf("按序号查找失败!未在表中找到数据!\n");
        return NULL;
    }
} 

//按内容查找
cNode searchAsContent(cNode L){
    Node *p;
    char data;
    int i = 1; 
    
    p = L->next;
    printf("\n请输入查找内容:");
    scanf("%c",p->data);
            return p;
        }
        p = p->next;
        i++;
    }
    
    //未找到数据
    if(p == L){
        printf("按内容查找失败!未在表中找到数据!\n");
    }
}  

//插入
cNode insertCNode(cNode L){
    Node *pre,&data);
    getchar();
    
    //插入位置不可能为负 
    if(i <= 0){
        printf("插入失败!插入位置不合法!插入位置不能为负\n");
        return NULL;
    }
    
    ////遍历完表且未找到插入位置(此时i大于表的长度) 或 找到插入位置时退出函数 退出循环
    while(pre != L && k < i - 1){
        pre = pre->next;
        k++;
    }
    
    if(pre == L){
        
        // 未找到插入位置(此时i大于表的长度)
        printf("插入失败!插入位置不合法!插入位置超出表的长度\n");
        return NULL;
    }else{
        
        //找到插入位置并插入数据     ,注意:pre指向插入位置的前一个结点  
        s = (cNode)malloc(sizeof(Node));
        s->data = data;
        s->next = pre->next;
        pre->next = s;
        L->len++; 
        printf("插入成功!");
        print(L);
        return L;
    }
} 

//删除 
cNode delList(cNode L){
    Node *pre,&i); 
    getchar();
    
    //删除的位置必须合法 
    if(i > L->len || i<= 0){
        printf("删除的位置超出了链表的长度!\n");
        return;
    }
    
    // 找到删除位置退出
    while(pre->next != L && k < i - 1){
        pre = pre->next;
        k++;
    }
    
    //删除操作 
    r = pre->next;
    pre->next = r->next;
    free(r);
    L->len--;
    printf("删除成功!\n"); 
    print(L);
    return L;
} 

//查看链表 
cNode print(cNode L){
    Node *p;
    int i = 0; 
    p = L->next;
    printf("查看链表数据为...\n");
    printf("共有 %d 个数据(注意序号是从头结点0开始,即第一个数据序号为 1)\n",p->data);
        p = p->next;
    } 
    printf("\n\n");
    return L;
}

int main(void){
    Node *L;
    printf("===============================循环单链表=====================================\n");
    initCNode(&L);
    createFromTail(L);
    searchAsNum(L);
    searchAsContent(L);
    insertCNode(L);
    delList(L);
    printf("===============================循环单链表=====================================\n");
    return 0;
} 
循环单链表

 

运行结果:

 

 

双向链表

定义

双向链表的的指针域在前面说过,它有两个指针域,一个指针域指向本结点的直接前驱,另一个则指向直接后继

 定义:

typedef struct DNode{
    char data;
    int len;
    struct DNode *prior;
    struct DNode *next;
}DNode,*DList;

 

创建

//尾插法创建 
DList createFromTail(DList L){
    DNode *s,*r;
    int flag = 1;
    char data;
    r = L;
    printf("尾插法建立表,&data);
        getchar();
        if(data == '#'){
            //若输入 # 则退出 
            flag = 0;
        }else{
            s = (DList)malloc(sizeof(DNode));
            s->data = data;
            s->prior = r;
            s->next = L;
            r->next = s;
            r = s;
            L->prior = r;
            (L->len)++; 
            flag = 1;
        }
    }
    printf("结束输入...\n");
    print(L);
    return L;
}

 

基本操作

查找
//按内容查找 
DList searchAsContent(DList L){
    DNode *p;
    char data;
    int i = 1; 
    
    p = L->next;
    printf("\n请输入查找内容:");
    scanf("%c",p->data);
            return p;
        }
        p = p->next;
        i++;
    }
    
    //未找到数据
    if(p == L){
        printf("按内容查找失败!未在表中找到数据!\n");
    }
}  

 

插入
//插入
DList insertDList(DList L){
    DNode *pre,&data);
    getchar();
    
    //插入位置不可能为负 
    if(i <= 0){
        printf("插入失败!插入位置不合法!插入位置不能为负\n");
        return NULL;
    }
    
    ////遍历完表且未找到插入位置(此时i大于表的长度) 或 找到插入位置时退出函数 退出循环
    while(pre != L && k < i - 1){
        pre = pre->next;
        k++;
    }
    
    if(pre == L){
        
        // 未找到插入位置(此时i大于表的长度)
        printf("插入失败!插入位置不合法!插入位置超出表的长度\n");
        return NULL;
    }else{
        
        //找到插入位置并插入数据,注意:pre指向插入位置的前一个结点 
        s = (DNode*)malloc(sizeof(DNode));
        s->data = data;
        
        s->next = pre->next;
        pre->next->prior = s;
        s->prior = pre; 
        pre->next = s;
        
        L->len++;
        printf("插入成功!");
        print(L);
        return L;
    }
}  

 

删除
DList delDList(DList L){
    DNode *p;
    int k = 1,i;
    p = L->next;
    printf("请输入删除的数据的位置(格式:address):");
    scanf("%d",&i); 
    getchar();
    
    //删除的位置必须合法 
    if(i > L->len || i<= 0){
        printf("删除的位置超出了链表的长度!\n");
        return;
    }
    
    // 找到删除位置退出
    while(p != L && k < i){
        p = p->next;
        k++;
    }
    
    //删除操作 
    p->next->prior = p->prior;
    p->prior->next = p->next;
    //上面两步可以互换顺序 
    
    free(p);
    L->len--;
    printf("删除成功!\n"); 
    print(L);
    return L;
} 

完整代码:

/*
双向链表 
*/
#include<stdio.h>
#include<stdlib.h>

typedef struct DNode{
    char data;
    int len;
    struct DNode *prior;
    struct DNode *next;
}DNode,*DList;

DList initDList(DList *L);
DList createFromTail(DList L);
DList searchAsNum(DList L);
DList searchAsContent(DList L);
DList insertDList(DList L);
DList delDList(DList L);
DList print(DList L);

//初始化
DList initDList(DList *L){
    (*L) = (DList)malloc(sizeof(DNode));
    (*L)->len = 0;
    (*L)->prior = (*L);
    (*L)->next = (*L);
    return *L;
} 

//尾插法创建 
DList createFromTail(DList L){
    DNode *s,&data);
        getchar();
        if(data == '#'){
            //若输入 # 则退出 
            flag = 0;
        }else{
            s = (DList)malloc(sizeof(DNode));
            s->data = data;
            s->prior = r;
            s->next = L;
            r->next = s;
            r = s;
            L->prior = r;
            (L->len)++; 
            flag = 1;
        }
    }
    printf("结束输入...\n");
    print(L);
    return L;
}

//按序号查找
DList searchAsNum(DList L){
    int i,j;
    DNode *p;
    
    p = L; 
    j = 0; 
    printf("\n请输入查找的序号: ");
    scanf("%d",&i);
    getchar();
    
    //查找的序号不可能为负 
    if(i <= 0){
        printf("输入不合法! \n");
        return NULL;
    }
    
    //退出情况有两种:表遍历完毕没有找到数据 或 p指针指向目标结点 
    while((p->next != L) && (j < i)){
        p = p->next;
        j++;
    }
    
    if(j == i){
        //找到结点 
        printf("按序号查找成功,p->data);
        return p;
    }else{
        //未找到 
        printf("按序号查找失败!未在表中找到数据!\n");
        return NULL;
    }
}

//按内容查找 
DList searchAsContent(DList L){
    DNode *p;
    char data;
    int i = 1; 
    
    p = L->next;
    printf("\n请输入查找内容:");
    scanf("%c",p->data);
            return p;
        }
        p = p->next;
        i++;
    }
    
    //未找到数据
    if(p == L){
        printf("按内容查找失败!未在表中找到数据!\n");
    }
}  

//插入
DList insertDList(DList L){
    DNode *pre,注意:pre指向插入位置的前一个结点 
        s = (DNode*)malloc(sizeof(DNode));
        s->data = data;
        
        s->next = pre->next;
        pre->next->prior = s;
        s->prior = pre; 
        pre->next = s;
        
        L->len++;
        printf("插入成功!");
        print(L);
        return L;
    }
}  

//删除 
DList delDList(DList L){
    DNode *p;
    int k = 1,&i); 
    getchar();
    
    //删除的位置必须合法 
    if(i > L->len || i<= 0){
        printf("删除的位置超出了链表的长度!\n");
        return;
    }
    
    // 找到删除位置退出
    while(p != L && k < i){
        p = p->next;
        k++;
    }
    
    //删除操作 
    p->next->prior = p->prior;
    p->prior->next = p->next;
    //上面两步可以互换顺序 
    
    free(p);
    L->len--;
    printf("删除成功!\n"); 
    print(L);
    return L;
} 

//查看表 
DList print(DList L){
    DNode *p;
    int i = 0; 
    p = L->next;
    printf("查看链表数据为...\n");
    printf("共有 %d 个数据(注意序号是从头结点0开始,即第一个数据序号为 1)\n",p->data);
        p = p->next;
    } 
    printf("\n\n");
    return L;
}

int main(void){
    DNode *L;
    printf("===============================双向链表=====================================\n");
    initDList(&L);
    createFromTail(L);
    searchAsNum(L);
    searchAsContent(L);
    insertDList(L);
    delDList(L);
    printf("===============================双向链表=====================================\n");
    return 0;
}
双向链表

 

运行结果:

 

 

顺序表和链表的比较总结

在数据结构绪论中就有提及算法的性能评价,既要从时间方面考虑(时间复杂度),又要从空间方面考虑(空间复杂度)。线性表在物理结构上的存储造成了顺序表和链表性能上的差异:顺序表使用一组连续的物理地址存储(逻辑上通常为数组,能够随机存取),而链表的物理存储地址是任意的(不能随机存取)。如此一来,线性表的基本操作会受到影响。

基于时间

适合顺序表的情况:因为顺序表通常是用数组表示决定了顺序表能够随机存储,所以当线性表的操作主要是进行查找而很少做插入和删除操作时应用顺序表

适合链表的情况:与顺序表的情况想反,需要频繁的进行插入或删除操作的线性表应用链表

 

基于空间

存储密度,是指结点数据本身所占的存储量和整个结点结构所占的存储量之比。

由前面提到顺序表和链表的物理存储地址可知:顺序表是静态分配的,链表是动态分配。静态分配的一个缺点就只,分配时空间有多大就是多大,存储规模是确定的,若数据元素无法用完静态分配的空间,就会造成浪费,存储密度小,空间利用率低。而动态分配则不会有这种情况。

因此,当线性表的长度变化不大,易于事先确定其存储规模时宜采用顺序表,反之应采用链表。

 

线性表链式存储方式比较
线性表链式存储方式比较
链表名称/操作名称 找表中首元素结点 找表尾元素结点 找p结点的前驱结点
带头结点的单链表L L->next 时间耗费O(1) 一重循环时间耗费O(n) 无法找到
带头结点的循环单链表L L->next 时间耗费O(1) 一重循环时间耗费O(n) 顺p结点的next域可以找到,时间耗费O(n)
带尾指针的循环单链表R R->next ->next 时间耗费O(1) 一重循环时间耗费O(1) 顺p结点的next域可以找到,时间耗费O(n)
到头结点的双向链表L L->next 时间耗费O(1) 一重循环时间耗费O(1) p->prior就是p的前驱结点,时间耗费O(1)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


【啊哈!算法】算法3:最常用的排序——快速排序       上一节的冒泡排序可以说是我们学习第一个真正的排序算法,并且解决了桶排序浪费空间的问题,但在算法的执行效率上却牺牲了很多,它的时间复杂度达到了O(N2)。假如我们的计算机每秒钟可以运行10亿次,那么对1亿个数进行排序,桶排序则只需要0.1秒,而冒泡排序则需要1千万秒,达到115天之久,是不是很吓人。那有没有既不浪费空间又可以快一点的排序算法
匿名组 这里可能用到几个不同的分组构造。通过括号内围绕的正则表达式就可以组成第一个构造。正如稍后要介绍的一样,既然也可以命名组,大家就有考虑把这个构造作为匿名组。作为一个实例,请看看下列字符串: “08/14/57 46 02/25/59 45 06/05/85 18 03/12/88 16 09/09/90 13“ 这个字符串就是由生日和年龄组成的。如果需要匹配年两而不要生日,就可以把正则
选择排序:从数组的起始位置处开始,把第一个元素与数组中其他元素进行比较。然后,将最小的元素方式在第0个位置上,接着再从第1个位置开始再次进行排序操作。这种操作一直到除最后一个元素外的每一个元素都作为新循环的起始点操作过后才终止。 public void SelectionSort() { int min, temp;
public struct Pqitem { public int priority; public string name; } class CQueue { private ArrayList pqueue; public CQueue() { pqueue
在编写正则表达式的时候,经常会向要向正则表达式添加数量型数据,诸如”精确匹配两次”或者”匹配一次或多次”。利用数量词就可以把这些数据添加到正则表达式里面了。 数量词(+):这个数量词说明正则表达式应该匹配一个或多个紧紧接其前的字符。 string[] words = new string[] { "bad", "boy", "baad", "baaad" ,"bear", "b
来自:http://blog.csdn.net/morewindows/article/details/6678165/归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列
插入排序算法有两层循环。外层循环会啄个遍历数组元素,而内存循环则会把外层循环所选择的元素与该元素在数组内的下一个元素进行比较。如果外层循环选择的元素小于内存循环选择的元素,那么瘦元素都想右移动以便为内存循环元素留出位置。 public void InsertionSort() { int inner, temp;
public int binSearch(int value) { int upperBround, lowerBound, mid; upperBround = arr.Length - 1; lowerBound = 0; while (lowerBound <= upper
虽然从表内第一个节点到最后一个节点的遍历操作是非常简单的,但是反向遍历链表却不是一件容易的事情。如果为Node类添加一个字段来存储指向前一个节点的连接,那么久会使得这个反向操作过程变得容易许多。当向链表插入节点的时候,为了吧数据复制给新的字段会需要执行更多的操作,但是当腰吧节点从表移除的时候就能看到他的改进效果了。 首先需要修改Node类来为累增加一个额外的链接。为了区别两个连接,这个把指
八、树(Tree)树,顾名思义,长得像一棵树,不过通常我们画成一棵倒过来的树,根在上,叶在下。不说那么多了,图一看就懂:当然了,引入了树之后,就不得不引入树的一些概念,这些概念我照样尽量用图,谁会记那么多文字?树这种结构还可以表示成下面这种方式,可见树用来描述包含关系是很不错的,但这种包含关系不得出现交叉重叠区域,否则就不能用树描述了,看图:面试的时候我们经常被考到的是一种叫“二叉树”的结构,二叉
Queue的实现: 就像Stack类的实现所做的一样,Queue类的实现用ArrayList简直是毋庸置疑的。对于这些数据结构类型而言,由于他们都是动态内置的结构,所以ArrayList是极好的实现选择。当需要往队列中插入数据项时,ArrayList会在表中把每一个保留的数据项向前移动一个元素。 class CQueue { private ArrayLis
来自:http://yingyingol.iteye.com/blog/13348911 快速排序介绍:快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地
Stack的实现必须采用一种基本结构来保存数据。因为再新数据项进栈的时候不需要担心调整表的大小,所以选择用arrayList.using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Collecti
数组类测试环境与排序算法using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace Data_structure_and_algorithm{ class CArray { pr
一、构造二叉树 二叉树查找树由节点组成,所以需要有个Node类,这个类类似于链表实现中用到的Node类。首先一起来看看Node类的代码。 public class Node { public int Data; public Node Left; public Node Right; public v
二叉树是一种特殊的树。二叉树的特点是每个结点最多有两个儿子,左边的叫做左儿子,右边的叫做右儿子,或者说每个结点最多有两棵子树。更加严格的递归定义是:二叉树要么为空,要么由根结点、左子树和右子树组成,而左子树和右子树分别是一棵二叉树。 下面这棵树就是一棵二叉树。         二叉树的使用范围最广,一棵多叉树也可以转化为二叉树,因此我们将着重讲解二叉树。二叉树中还有连两种特殊的二叉树叫做满二叉树和
上一节中我们学习了队列,它是一种先进先出的数据结构。还有一种是后进先出的数据结构它叫做栈。栈限定只能在一端进行插入和删除操作。比如说有一个小桶,小桶的直径只能放一个小球,我们现在向小桶内依次放入2号、1号、3号小球。假如你现在需要拿出2号小球,那就必须先将3号小球拿出,再拿出1号小球,最后才能将2号小球拿出来。在刚才取小球的过程中,我们最先放进去的小球最后才能拿出来,而最后放进去的小球却可以最先拿
msdn中的描述如下:(?= 子表达式)(零宽度正预测先行断言。) 仅当子表达式在此位置的右侧匹配时才继续匹配。例如,w+(?=d) 与后跟数字的单词匹配,而不与该数字匹配。此构造不会回溯。(?(零宽度正回顾后发断言。) 仅当子表达式在此位置的左侧匹配时才继续匹配。例如,(?此构造不会回溯。msdn描述的比较清楚,如:w+(?=ing) 可以匹配以ing结尾的单词(匹配结果不包括ing),(
1.引入线索二叉树 二叉树的遍历实质上是对一个非线性结构实现线性化的过程,使每一个节点(除第一个和最后一个外)在这些线性序列中有且仅有一个直接前驱和直接后继。但在二叉链表存储结构中,只能找到一个节点的左、右孩子信息,而不能直接得到节点在任一遍历序列中的前驱和后继信息。这些信息只有在遍历的动态过程中才能得到,因此,引入线索二叉树来保存这些从动态过程中得到的信息。 2.建立线索二叉树 为了保
排序与我们日常生活中息息相关,比如,我们要从电话簿中找到某个联系人首先会按照姓氏排序、买火车票会按照出发时间或者时长排序、买东西会按照销量或者好评度排序、查找文件会按照修改时间排序等等。在计算机程序设计中,排序和查找也是最基本的算法,很多其他的算法都是以排序算法为基础,在一般的数据处理或分析中,通常第一步就是进行排序,比如说二分查找,首先要对数据进行排序。在Donald Knuth 的计算机程序设