文章来源:http://pjf.name/post-109.html
本文基于"姓名标识-非商业性-相同方式分享 4.0国际"协议创作或转载,转载原创文章请注明来源于疯子的自留地,否则请勿转载或再转载,谢谢合作:)
恩,<<大话数据结构>>第三章啃完了,做下笔记,一是回顾,二是自己写下代码练下手感,毕竟还是那句老话"无它,唯手熟尔"不是
首先是线性表(List)的定义:
定义:0个或者多个数据元素组成的有限序列.(注意"有限"这个词)
抽象数据类型:
01 |
1.每个元素类型都为DateType |
02 |
操作 |
03 |
1.初始化线性表 |
04 |
2.检测现行是否为空 |
05 |
3.清空线性表 |
06 |
4.取得线性表上某个元素的值 |
07 |
5.查找线性表上某个元素的值 |
08 |
6.将某个元素插入线性表 |
09 |
7.删除线性表上某个元素 |
10 |
8.检测线性表的元素个数 |
线性表的思维导图:
先来瞅顺序存储结构,其实说白了,就是分配一个数组,当然,下面我就搞一维就行了,N维的...额,等我写我的账本程序的时候再说吧
首先定义以下结构:
1 |
#define MAXSIZE 50 |
1 |
typedef { |
2 |
int Data[MAXSIZE]; //欲插入的数据 |
3 |
int Length; //线性表的长度 |
4 |
}List; //初始化线性表 |
先来把几个操作的函数给写好了:)
1.初始化
1 |
int Length; //线性表的长度 |
2 |
List NewList[Length]; |
2.插入
01 |
BOOL InsertList(List* L, int InsertData, int InsertPostion) |
02 |
{ |
03 |
int i; |
04 |
05 |
if (MAXSIZE==L->Length) |
06 |
return FALSE; //if the list is full,return false |
07 |
08 |
if (L->Length<1||L->Length+1 |
09 |
return FALSE; //if the insert location is not right or the list is empty, return false |
10 |
11 |
if (InsertPostion<=L->Length) //if the insert location is not the end of the list |
12 |
{ |
13 |
for (i=L->Length-1;i>=InsertPostion-1;i--) |
14 |
{ |
15 |
L->Data[i+1]=L->Data[i]; |
16 |
} |
17 |
} |
18 |
19 |
L->Data[InsertPostion-1]=InsertData; //insert the data to the relevant location |
20 |
L->Length++; //add the list's length |
21 |
22 |
return TRUE; |
23 |
} |
3.删除
01 |
BOOL DeleteList(List* L, int DeleteData, int DeletePostion) //delete the number of the relevent position |
02 |
{ |
03 |
int i; |
04 |
05 |
if (0==L->Length) // if the list is empty,return false |
06 |
return FALSE; |
07 |
08 |
if (DeletePostion>L->Length||DeletePostion<1) //if the delete position is wrong,return false |
09 |
return FALSE; |
10 |
11 |
if (DeletePostion |
12 |
{ |
13 |
for (i=DeletePostion-1;i<=L->Length;i++) |
14 |
{ |
15 |
L->Data[i]=L->Data[i+1]; |
16 |
} |
17 |
} |
18 |
19 |
L->Length--; //subtract 1 to the list length |
20 |
21 |
return TRUE; |
22 |
} |
4.清空
01 |
BOOL EmptyList(List* L) |
02 |
{ |
03 |
int i; |
04 |
05 |
if (0==L->Length) //if the list is empty,return false |
06 |
return FALSE; |
07 |
08 |
for (i=0;i |
09 |
L->Data[i]=0; |
10 |
return TRUE; |
11 |
} |
5.查找
01 |
BOOL SearchPostionValueList( const List* L, int SearchPosition, int *Output) //search the value of relevant position |
02 |
{ |
03 |
if (0==L->Length||SearchPosition>L->Length) //if the length of the list is 0 or the search position is void,return false |
04 |
return FALSE; |
05 |
06 |
*e=L->Data[SearchPosition-1]; |
07 |
08 |
return TRUE; |
09 |
} |
10 |
11 |
BOOL SearchValueList( const List* L, int SearchData) //search if the value is in the list |
12 |
{ |
13 |
int i; |
14 |
15 |
if (0==L->Length) //if the length of the list is 0,return false |
16 |
return FALSE; |
17 |
18 |
for (i=0;i |
19 |
{ |
20 |
if (SearchData==L->Data[i]) |
21 |
break ; |
22 |
} |
23 |
24 |
if (i==L->Length) |
25 |
return FALSE; |
26 |
else |
27 |
return L->Data[i]; |
28 |
} |
关于优缺点:
由上面的我写的代码片段也可以看出来了,在清空,插入和删除操作的时候时间复杂度是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数据项就可以用来保存链表的长度值,这样取得某个值得时候就可以通过查看这个值来看查找的那个位置是否是合法的...
好了,说了那么多,咱们来见下庐山真面目...
看下单链表:
单链表呢,就是,就是,哎,还是直接看下单链表的数据结构形式吧...
1 |
typedef struct Node{ |
2 |
char Data; |
3 |
struct Node* next; |
4 |
}Node; |
接下来,我们看下单链表的插入,查找,删除,清空以及初始化那些怎么做.
1.创建(有头插法和尾插法)
01 |
BOOL CreateList(List* L, int Length) //尾插法 |
02 |
{ |
03 |
List EndNode,NewEndNode; |
04 |
int temp; |
05 |
char Chart= 'a' ; |
06 |
07 |
*L=(List) malloc ( sizeof (Node)); |
08 |
if (!L) |
09 |
return false ; |
10 |
(*L)->Data=0; //初始化链表结点数为0 |
11 |
EndNode=*L; |
12 |
13 |
for (temp=0;temp |
14 |
{ |
15 |
NewEndNode=(Node*) malloc ( sizeof (Node)); //给新的尾结点分配内存 |
16 |
if (!NewEndNode) |
17 |
return false ; |
18 |
NewEndNode->Data=Chart; //相应的值赋给新的尾结点 |
19 |
EndNode->next=NewEndNode; //让原来的尾结点的next值指向新的尾结点 |
20 |
EndNode=NewEndNode; //让原来的尾结点成为新的尾结点(这里非常重要也非常令人困惑,不熟悉C指针的话这里非常容易跌倒,我们要明白EndNode和NewEndNode里面存放的是结点的内存地址,即EndNode和NewEndNode都是*Node,而L则是指针的指针,只有*L才是*Node,**L是Node,进行单步调试的时候发现L,EndNode,NewNode的地址是不变的,变得是新增结点的地址,也就是EndNode和NewNode里面的值,这样就不断地新增了结点同时给结点内的数据初始化了,因为L是头指针,所以我们可以不断根据它的next值来找到下一个结点的值,如果我这里有什么表述不清的,建议单步调试跟踪一下各个变量的值就清楚了:) |
21 |
} |
22 |
EndNode->next=NULL; //让尾结点指向NULL |
23 |
(*L)->Data=( char )temp; //头指针的Data别浪费了,让它保存链表的结点数吧,哈哈:) |
24 |
return true ; |
25 |
} |
26 |
BOOL CreateHeadList(List *L, int Length) //头插法 |
27 |
{ |
28 |
List Header; |
29 |
int temp; |
30 |
char Charct= 'a' ; |
31 |
32 |
*L=(List) malloc ( sizeof (Node)); //给头指针分配内存 |
33 |
if (!L) |
34 |
return false ; |
35 |
(*L)->Data=0; //初始化链表结点数为0 |
36 |
for (temp=0;temp |
37 |
{ |
38 |
Header=(List) malloc ( sizeof (Node)); //给新的头结点分配内存 |
39 |
if (!Header) |
40 |
return false ; |
41 |
Header->Data=Charct; //给新的头结点赋予相应的值 |
42 |
Header->next=(*L)->next; //让新的头结点的next指向就的头结点的地址 |
43 |
(*L)->next=Header; //让头指针的next指向新的头结点的地址 |
44 |
} |
45 |
(*L)->Data=( char )Length; //头指针的Data别浪费了,让它保存链表的结点数吧,哈哈:) |
46 |
47 |
return true ; |
48 |
} |
2.遍历
01 |
BOOL LookUpList(List L) |
02 |
{ |
03 |
int length,temp; |
04 |
List CurrentNode; |
05 |
06 |
if (0==L->Data) //如果链表为空,返回false |
07 |
return false ; |
08 |
09 |
length=( int )L->Data; //取得链表长度 |
10 |
CurrentNode=L->next; //让CurrenNode指向头结点 |
11 |
12 |
for (temp=1;temp<=length;temp++) |
13 |
{ |
14 |
printf ( "%3c" ,CurrentNode->Data); //输出当前结点的数据 |
15 |
if (0==temp%6) //每隔6个数据则换行 |
16 |
printf ( "\n" ); |
17 |
CurrentNode=CurrentNode->next; //当前结点后移一位 |
18 |
} |
19 |
printf ( "\n" ); |
20 |
return true ; |
21 |
} |
3.插入
01 |
BOOL InsertListData(List *L, int InsertLocation, char InsertData) |
02 |
{ |
03 |
List InsertNode,InsertNodePre; |
04 |
int temp; |
05 |
06 |
if (InsertLocation<1) //如果需要插入的位置小于1,则假想插入位置为头结点 |
07 |
InsertLocation=1; |
08 |
09 |
InsertNodePre=*L; |
10 |
11 |
for (temp=1;temp //一直移动到需要插入的位置的前一个结点 |
12 |
{ |
13 |
InsertNodePre=InsertNodePre->next; |
14 |
} |
15 |
16 |
InsertNode=(List) malloc ( sizeof (Node)); //给要插入的结点分配内存 |
17 |
if (!InsertNode) |
18 |
return false ; |
19 |