白驹过隙,这篇文章距今已有一年以上的历史。技术发展日新月异,文中的观点或代码很可能过时或失效,请自行甄别:)

恩,<<大话数据结构>>第三章啃完了,做下笔记,一是回顾,二是自己写下代码练下手感,毕竟还是那句老话"无它,唯手熟尔"不是

首先是线性表(List)的定义:

定义:0个或者多个数据元素组成的有限序列.(注意"有限"这个词)

抽象数据类型:

1.每个元素类型都为DateType
操作
    1.初始化线性表
    2.检测现行是否为空
    3.清空线性表
    4.取得线性表上某个元素的值
    5.查找线性表上某个元素的值
    6.将某个元素插入线性表
    7.删除线性表上某个元素
    8.检测线性表的元素个数

线性表的思维导图:

Untitled Diagram (3).png

先来瞅顺序存储结构,其实说白了,就是分配一个数组,当然,下面我就搞一维就行了,N维的...额,等我写我的账本程序的时候再说吧

首先定义以下结构:

#define MAXSIZE 50

typedef{
    int Data[MAXSIZE];//欲插入的数据
    int Length;//线性表的长度
}List;//初始化线性表

先来把几个操作的函数给写好了:)

1.初始化

int Length;//线性表的长度
List NewList[Length];

2.插入

BOOL InsertList(List* L,int InsertData,int InsertPostion)
{
    int i;

    if(MAXSIZE==L->Length)
        return FALSE;//if the list is full,return false

    if(L->Length<1||L->Length+1<InsertPostion)
        return FALSE;//if the insert location is not right or the list is empty, return false

    if(InsertPostion<=L->Length)//if the insert location is not the end of the list
    {
        for(i=L->Length-1;i>=InsertPostion-1;i--)
        {
            L->Data[i+1]=L->Data[i];
        }
    }

    L->Data[InsertPostion-1]=InsertData;//insert the data to the relevant location
    L->Length++;//add the list's length

    return TRUE;
}

3.删除

BOOL DeleteList(List* L,int DeleteData,int DeletePostion)//delete the number of the relevent position
{
    int i;

    if(0==L->Length)// if the list is empty,return false
        return FALSE;

    if(DeletePostion>L->Length||DeletePostion<1)//if the delete position is wrong,return false
        return FALSE;

    if(DeletePostion<L->Length)
    {
        for(i=DeletePostion-1;i<=L->Length;i++)
        {
            L->Data[i]=L->Data[i+1];
        }
    }

    L->Length--;//subtract 1 to the list length 

    return TRUE;
}

4.清空

BOOL EmptyList(List* L)
{
    int i;

    if(0==L->Length)//if the list is empty,return false
        return FALSE;

    for(i=0;i<L->Length;i++)
        L->Data[i]=0;
    return TRUE;
}

5.查找

BOOL SearchPostionValueList(const List* L,int SearchPosition,int *Output)//search the value of relevant position
{
    if(0==L->Length||SearchPosition>L->Length)//if the length of the list is 0 or the search position is void,return false
        return FALSE;

    *e=L->Data[SearchPosition-1];

    return TRUE;
}

BOOL SearchValueList(const List* L,int SearchData)//search if the value is in the list
{
    int i;

    if(0==L->Length)//if the length of the list is 0,return false
        return FALSE;

    for(i=0;i<L->Length;i++)
    {
        if(SearchData==L->Data[i])
            break;
    }

    if(i==L->Length)
        return FALSE;
    else
        return L->Data[i];
}

关于优缺点:

由上面的我写的代码片段也可以看出来了,在清空,插入和删除操作的时候时间复杂度是O(n),但是读取某个数据的时候时间复杂度为O(1).所以优缺点也就很明显了.顺序存储适合那些频繁读操作的程序,比如说什么呢?比如说...比如说...额,比如说我写一个从今年(2014)到1000年后(一千年,你我在哪里了呢?)中每年是否为闰年的程序,要是每次都去计算的话有点麻烦,咱先把它这1000年中每年是否是闰年用一个顺序存储结构给存起来,然后就可以直接找了,嘿嘿,时间复杂度可是O(1)呢,不过就是有点浪费空间,不过对于现在都是土豪级别的电脑来说这点空间怕什么呢,嘿嘿但是...但是我要是频繁读取数据怎么办!怎么办!!!别急,咱们的老前辈们早就考虑到了,这不,咱们接下来就来鼓捣下----链表.

上面的思维导图也看到链表有好几种呢,还是先来看下链表的定义是什么:

定义:<<大话>>上没讲,我的理解就是一串内存中不连续的空间根据某种联系然后就组织成了一种one to one的结构.

关于定义:

一定得是one to one,不能使N to one,也不能使one to N,借用<<大话>>上的例子就是十二星座就是一个典型的例子,当然生肖也是,不过话说这也是线性表的吧,比如说刚刚说的顺序存储结构.

还有以下几个定义也要明白,头结点和头指针,那么到底什么是头结点和头指针呢?可以看下<<大话>>里面讲的,很清楚,不过既然是自我复习嘛,自己按照自己的话来讲一遍也是不错的,嘿嘿.其实我觉得很简单啊,头指针其实也是一个数据,不过它指向的是链表第一个结点的内存地址,而头结点,当然就是含有咱们第一个数据的那个结点咯,有人就说了,既然头指针也相当于一个结点,那这个头指针里面应该也有一个data的数据项吧,只用她的next数据项不是浪费了内存嘛.那个...win32函数里面的wndclass不是有个cbClsExtra数据项嘛,可用可不用呗,比如咱们头指针的data数据项就可以用来保存链表的长度值,这样取得某个值得时候就可以通过查看这个值来看查找的那个位置是否是合法的...

好了,说了那么多,咱们来见下庐山真面目...

看下单链表:

单链表呢,就是,就是,哎,还是直接看下单链表的数据结构形式吧...

typedef struct Node{
    char Data;
    struct Node* next;
}Node;

接下来,我们看下单链表的插入,查找,删除,清空以及初始化那些怎么做.

1.创建(有头插法和尾插法)

BOOL CreateList(List* L,int Length)//尾插法 
{
    List EndNode,NewEndNode;
    int temp;
    char Chart='a';

    *L=(List)malloc(sizeof(Node));
    if(!L)
        return false;
    (*L)->Data=0;//初始化链表结点数为0 
    EndNode=*L;

    for(temp=0;temp<Length;temp++,Chart++)
    {
        NewEndNode=(Node*)malloc(sizeof(Node));//给新的尾结点分配内存
        if(!NewEndNode)
            return false;
        NewEndNode->Data=Chart; //相应的值赋给新的尾结点
        EndNode->next=NewEndNode;//让原来的尾结点的next值指向新的尾结点
        EndNode=NewEndNode;    //让原来的尾结点成为新的尾结点(这里非常重要也非常令人困惑,不熟悉C指针的话这里非常容易跌倒,我们要明白EndNode和NewEndNode里面存放的是结点的内存地址,即EndNode和NewEndNode都是*Node,而L则是指针的指针,只有*L才是*Node,**L是Node,进行单步调试的时候发现L,EndNode,NewNode的地址是不变的,变得是新增结点的地址,也就是EndNode和NewNode里面的值,这样就不断地新增了结点同时给结点内的数据初始化了,因为L是头指针,所以我们可以不断根据它的next值来找到下一个结点的值,如果我这里有什么表述不清的,建议单步调试跟踪一下各个变量的值就清楚了:)
    }
    EndNode->next=NULL;//让尾结点指向NULL
    (*L)->Data=(char)temp;//头指针的Data别浪费了,让它保存链表的结点数吧,哈哈:)    
    return true;
}
BOOL CreateHeadList(List *L,int Length)//头插法 
{
    List Header;
    int temp;
    char Charct='a';

    *L=(List)malloc(sizeof(Node));//给头指针分配内存
    if(!L)
        return false;
    (*L)->Data=0;//初始化链表结点数为0 
    for(temp=0;temp<Length;temp++,Charct++)
    {
        Header=(List)malloc(sizeof(Node));//给新的头结点分配内存
        if(!Header)
            return false;
        Header->Data=Charct;//给新的头结点赋予相应的值
        Header->next=(*L)->next;//让新的头结点的next指向就的头结点的地址
        (*L)->next=Header;//让头指针的next指向新的头结点的地址
    }
    (*L)->Data=(char)Length;//头指针的Data别浪费了,让它保存链表的结点数吧,哈哈:)    

    return true;
}

2.遍历

BOOL LookUpList(List L)
{
    int length,temp;
    List CurrentNode;

    if(0==L->Data)//如果链表为空,返回false
        return false;

    length=(int)L->Data;//取得链表长度    
    CurrentNode=L->next;//让CurrenNode指向头结点

    for(temp=1;temp<=length;temp++)
    {
        printf("%3c",CurrentNode->Data);//输出当前结点的数据
        if(0==temp%6)//每隔6个数据则换行
            printf("\n");
        CurrentNode=CurrentNode->next;//当前结点后移一位
    }
    printf("\n");
    return true;
}

3.插入

BOOL InsertListData(List *L,int InsertLocation,char InsertData)
{
    List InsertNode,InsertNodePre;
    int temp;

    if(InsertLocation<1)//如果需要插入的位置小于1,则假想插入位置为头结点 
        InsertLocation=1;

    InsertNodePre=*L;

    for(temp=1;temp<InsertLocation&&InsertNodePre->next;temp++)//一直移动到需要插入的位置的前一个结点 
    {
        InsertNodePre=InsertNodePre->next;
    }

    InsertNode=(List)malloc(sizeof(Node));//给要插入的结点分配内存
    if(!InsertNode)
        return false;

    InsertNode->Data=InsertData;//给要插入到的结点赋值
    InsertNode->next=InsertNodePre->next;//把要插入的结点的next指向以前要插入的前一个结点的后一个结点
    InsertNodePre->next=InsertNode;//插入的前一个结点的next指向要插入的结点地址
    (*L)->Data++;//链表长度加1

    return true;
}

4.删除

BOOL DeleteValueInList(List *L,int number,char *OutputData)
{
    int temp;
    List DeleteNodePre,DeleteNode,DeleteNodeNext;

    if(0==(*L)->Data||number>(*L)->Data)//链表为空或者删除的位置不正常 
        return false;

    DeleteNodePre=*L;

    for(temp=1;temp<number&&DeleteNodePre->next;temp++)//移动到要删除的结点的前一个结点 
    {
        DeleteNodePre=DeleteNodePre->next;
    }

    DeleteNode=DeleteNodePre->next;//取得要删除的结点的地址 
    DeleteNodeNext=DeleteNode->next;//取得要删除的结点的下一个结点的地址 
    DeleteNodePre->next=DeleteNodeNext;//将要删除的下一个结点的地址复制给要删除的前一个结点的next变量
    *OutputData=DeleteNode->Data;//要删除的结点的数据复制给OutputData变量
    free(DeleteNode);//释放要删除的结点的内存 
    (*L)->Data--;//链表的长度减1 

    return true;    
}

怎么样,看懂了没?写一个例子瞧一下,附代码:

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

#define true 1
#define false 0
#define BOOL int

typedef struct Node *List;

int main(void)
{
    List L,L2;
    int i=24;
    int DeleteNumber;
    char DeleteData,InsertData;

    if(true==CreateList(&L,i))
        printf("Initialize List success!\n");
    else
    {
        printf("Initialize List fail!\n");
        return 1;
    }
    printf("the List is:\n");
    LookUpList(L);
    if(true==CreateHeadList(&L2,i))
        printf("Initialize header List success!\n");
    else
    {
        printf("Initialize header List fail!\n");
        return 1;
    }
    printf("the list is:\n");    
    LookUpList(L2);
    printf("请输入要删除的链表(1,代表尾插法建立的链表,2代表头插法建立的链表):");
    if(1==scanf("%d",&DeleteNumber))
    {
        switch(DeleteNumber)
        {
        case 1:
            if(true==DeleteValueInList(&L,5,&DeleteData))
            {
                printf("删除第%d个链表的第5个数成功!删除的数据为:%c\n新的链表数据如下:\n",DeleteNumber,DeleteData);
                LookUpList(L);
            }
            break;    
        case 2:
            if(true==DeleteValueInList(&L2,5,&DeleteData))
            {
                printf("删除第%d个链表的第5个数成功!删除的数据为:%c\n新的链表数据如下:\n",DeleteNumber,DeleteData);
                LookUpList(L2);
            }
            break;    
        }
    }
    InsertData='z';
    printf("请输入要插入的链表(1,代表尾插法建立的链表,2代表头插法建立的链表):");
    if(1==scanf("%d",&DeleteNumber))
    {
        switch(DeleteNumber)
        {
        case 1:
            if(true==InsertListData(&L,32,InsertData))
            {
                printf("插入第%d个链表成功!插入的数据为:%c\n新的链表数据如下:\n",DeleteNumber,InsertData);
                LookUpList(L);
            }
            break;    
        case 2:
            if(true==InsertListData(&L2,32,InsertData))
            {
                printf("插入第%d个链表成功!插入的数据为:%c\n新的链表数据如下:\n",DeleteNumber,InsertData);
                LookUpList(L2);
            }
            break;    
        }
    }
    printf("请输入要清空的链表(1,代表尾插法建立的链表,2代表头插法建立的链表):");
    if(1==scanf("%d",&DeleteNumber))
    {
        switch(DeleteNumber)
        {
        case 1:
            if(true==EmptyList(&L))
            {
                printf("清空链表%d成功!新的链表长度为:%d\n",DeleteNumber,L->Data);
            }
            break;    
        case 2:
            if(true==EmptyList(&L2))
            {
                printf("清空链表%d成功!新的链表长度为:%d\n",DeleteNumber,L2->Data);
            }
            break;    
        }
    }
    return 0;
}

Capture.PNG

恩,其实还有循环链表,双向链表,静态链表,稍后再写,都差不多的