前言 这一节开始学习线性结构的知识,线性结构是指数据元素之间存在一对一的线性关系。
线性表是一个简单的线性结构,可以通过这一个数据结构了解更多的数据结构是实现。
一、逻辑结构 线性表是一个若干个数据元素的一个线性序列,它是一个有限序列,其中每个元素都是一个数据元素,每个元素都有一个前驱和一个后继,除了第一个元素没有前驱,最后一个元素没有后继。
可以了解线性表在存储结构中可以使用数组或者列表来进行存放,因为这个线性表是一个连续的序列。
二、存储结构 从逻辑结构那分析出来知道这个线性表可以用顺序存储,也可以用非顺序结构,这里两个存储方式都不会分别实现。
1.数据类型 在开始存储前先介绍一下存储的结构,这里的结构如下:
1 2 3 4 5 typedef struct { data_t data[MaxSize]; int length; } SqList, *SqListPtr;
data是存放数据的数组,length是存放数据的长度。
data_t是一个数据类型,这里可以使用int类型来进行存放。
MaxSize是数组的大小,这里可以使用宏定义来进行定义。
2.顺序存储 前面说过顺序存储是存放在一块地址连续的空间,我们可以用数组来进行存放,但是直接使用数组来进行存放就会有一个问题,直接使用数组那它开辟的空间是在栈上的,栈在程序结束时就会被释放,所以我们需要使用动态内存分配来进行存放,这样就会开辟到堆区上。
在堆区上会一直存放的,直到调用free函数来释放内存。
然后了解了使用过程后,我们可以开始实现顺序存储了。
在实现的时候先了解一下我们需要实现的功能,这里列出一些操作类型,等后面会一一实现:
1 2 3 4 5 6 7 8 9 SqListPtr InitList () ; Status DestroyList (SqListPtr* list ) ; Status ClearList (SqListPtr list ) ; Status ListEmpty (SqListPtr list ) ; int ListLength (SqListPtr list ) ; Status GetElem (SqListPtr list , int pos, data_t *elem) ; int LocateElem (SqListPtr list , data_t elem) ; Status ListInsert (SqListPtr list , int pos, data_t elem) ; Status ListDelete (SqListPtr list , int pos, data_t *elem) ;
然后还有一些宏定义和类型的重命名:
1 2 3 4 5 6 7 #define MaxSize 100 #define OK 1 #define ERROR -1 #define IS_NULL 0 #define IS_NOT_NULL 1 typedef int data_t ;typedef int Status;
然后就可以开始写代码了。
2.1 初始化线性表 实现需要初始化一下线性表,前面说过,需要使用动态内存分配来进行存放,所以需要使用malloc函数来进行分配内存,然后直接创建结构体即可。
然后给结构体中的length赋值为0即可,这个length是用来记录线性表中元素的个数的,有些喜欢从-1,然后这个代表最后一个元素的下标。而我们这记录的是元素个数,所以从0开始。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 SqListPtr InitList () { SqListPtr list = NULL ; list = (SqListPtr)malloc (sizeof (SqList)); if (list == NULL ) { return NULL ; } list ->length = 0 ; return list ; }
2.2 销毁线性表 对于顺序结构来说,销毁直接就释放那个结构体就可以了,因为是连续的空间,不需要释放每个元素。
代码如下:
1 2 3 4 5 6 7 8 9 10 Status DestroyList (SqListPtr* list ) { if (*list == NULL ) { return ERROR; } free (*list ); *list = NULL ; return OK; }
这里的参数指针不解释,其实就是二级指针的问题,大家有兴趣可以看我前面介绍的二级指针的内容。
2.3 清空线性表 清空其实就是将里面的元素全部清除,但是这里不需要这么麻烦,我们在使用这个线性表的时候是通过length
的记录来记录线性表中的个数,当这个length
为0的时候,就代表这个线性表中没有元素了,所以我们只需要将length
赋值为0即可。
代码如下:
1 2 3 4 5 6 7 8 9 Status ClearList (SqListPtr list ) { if (list == NULL ) { return ERROR; } list ->length = 0 ; return OK; }
2.4 判断线性表是否为空 判断线性表是否为空,只需要判断length
是否为0即可,为0就代表这个线性表中没有元素,不为0就代表这个线性表中有元素。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 Status ListEmpty (SqListPtr list ) { if (list == NULL ) { return ERROR; } if (list ->length == 0 ) { return IS_NULL; } return IS_NOT_NULL; }
2.5 获取线性表的长度 获取线性表的长度,只需要返回length
的值即可。
代码如下:
1 2 3 4 5 6 7 8 int ListLength (SqListPtr list ) { if (list == NULL ) { return ERROR; } return list ->length; }
2.6 获取线性表中指定位置的元素 获取线性表中指定位置的元素,只需要判断位置是否合法,然后返回指定位置的元素即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 Status GetElem (SqListPtr list , int pos, data_t *elem) { if (list == NULL || elem == NULL ) { return ERROR; } if (pos < 0 || pos >= list ->length) { return ERROR; } *elem = list ->data[pos]; return OK; }
2.7 获取线性表中指定元素的位置 获取线性表中指定元素的位置,只需要遍历线性表,然后判断元素是否相等,相等就返回位置,不相等就返回-1。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int LocateElem (SqListPtr list , data_t elem) { if (list == NULL ) { return ERROR; } int i = 0 ; for (i = 0 ; i < list ->length; i++) { if (list ->data[i] == elem) { return i; } } return ERROR; }
2.8 在线性表中指定位置插入元素 在线性表中指定位置插入元素,只需要判断位置是否合法,然后将指定位置后面的元素都向后移动一位,然后将元素插入到指定位置即可。
这里用一个图来演示这个过程:
这里分为两个情况,一个是在最后插入数据,这种情况不需要移动,另一种是在中间插入数据,这种情况需要移动:
首先有一个线性表,这里需要在位置2插入一个元素value,然后需要先找到一下这个线性表中的最后一个元素的下标,用一个指针进行存放,存放后比较一下这个指针存放的下标是否是需要插入的位置,如果是直接插入,如果不是需要让这个指针指向的内容向后移动:
然后指针又往前移动,再次比较是不是插入的位置,如果不是将这个指针存放的下标的内容向后移动:
如果这个指针指向的下标就是需要插入的位置,那先移动元素,移动完成后将指插入即可:
知道这个过程后我们就可以写一下代码了
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Status ListInsert (SqListPtr list , int pos, data_t elem) { int i = list ->length - 1 ; if (list == NULL ) { return ERROR; } if (pos < 0 || pos > list ->length) { return ERROR; } for (i = list ->length - 1 ; i >= pos; i--) { list ->data[i + 1 ] = list ->data[i]; } list ->data[pos] = elem; list ->length++; return OK; }
2.9 在线性表中指定位置删除元素 删除和插入一样,也是需要移动元素进行删除,前面插入是往后移动元素,而删除是往前移动,直接覆盖住需要删除的元素,然后让length
减1即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Status ListDelete (SqListPtr list , int pos, data_t *elem) { int i = 0 ; if (list == NULL ) { return ERROR; } if (pos < 0 || pos > list ->length) { return ERROR; } *elem = list ->data[pos]; for (i = pos; i < list ->length; i++) { list ->data[i] = list ->data[i + 1 ]; } list ->length--; return OK; }
2.10 打印线性表 打印线性表,只需要遍历线性表,然后打印每个元素即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 void PrintList (SqListPtr list ) { if (list == NULL ) { return ; } int i = 0 ; for (i = 0 ; i < list ->length; i++) { printf ("%d " , list ->data[i]); } printf ("\n" ); }
这样线性表的基本操作都做完了,下面来介绍一下链式存储,也就是非顺序存储。
3.链式存储 链式存储是指用一组任意的存储单元存储线性表中的数据元素,这组存储单元可以是连续的,也可以是不连续的。
链式存储是通过指针来进行连接的,每个元素都有一个指针,指向下一个元素,这样就可以通过指针来进行连接,这样就可以实现链式存储,链式存储的长度其实是根据堆区的大小来的,如果堆区空间还有内存,则可以创建结点。
所以在这里的链式存储结构就没有长度这个属性了,如果要判断这个线性表的长度,可以通过遍历链表来获取长度。
链式存储的结构如下:
1 2 3 4 typedef struct Node { data_t data; struct Node *next ; }ListNode, *ListPtr;
data是存放数据的,next是指向下一个结点的地址。
下面是链式存储的有些函数:
1 2 3 4 5 6 7 8 9 ListPtr InitList () ; Status DestroyList (ListPtr* list ) ; Status ClearList (ListPtr list ) ; Status ListEmpty (ListPtr list ) ; int ListLength (ListPtr list ) ; Status GetElem (ListPtr list , int pos, data_t *elem) ; int LocateElem (ListPtr list , data_t elem) ; Status ListInsert (ListPtr list , int pos, data_t elem) ; Status ListDelete (ListPtr list , int pos, data_t *elem) ;
3.1 初始化线性表 初始化线性表,只需要创建一个头结点,然后将头结点的指针指向NULL即可,这个头节点不需要任何的数据,相当于是一个指示的结点。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 ListPtr InitList () { ListPtr list = NULL ; list = (ListPtr)malloc (sizeof (ListNode)); if (list == NULL ) { return NULL ; } list ->data = -1 ; list ->next = NULL ; return list ; }
3.2 销毁线性表 销毁线性表,只需要遍历链表,然后释放每个结点即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Status DestroyList (ListPtr* list ) { ListPtr p = *list ; ListPtr q = (*list )->next; if (*list == NULL ) { return ERROR; } while (p) { free (p); p = q; q = q->next; } *list = NULL ; return OK; }
3.3 清空线性表 这里可以直接让头节点的next
指向NULL
即可,但是后面的结点还是在的,如果不进行释放,那可能会导致内存溢出,所以这里需要先将后面的结点释放掉,然后再将头节点的next
指向NULL
即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Status ClearList (ListPtr list ) { ListPtr p = list ->next; ListPtr q = list ->next; if (list == NULL ) { return ERROR; } while (q) { p = p->next; free (q); q = p; } list ->next = NULL ; return OK; }
3.4 判断线性表是否为空 判断线性表是否为空,只需要判断头节点的next
是否为NULL
即可,为NULL
就代表这个线性表中没有元素,不为NULL
就代表这个线性表中有元素。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 Status ListEmpty (ListPtr list ) { if (list == NULL ) { return ERROR; } if (list ->next == NULL ) { return IS_NULL; } return IS_NOT_NULL; }
3.5 获取线性表的长度 获取线性表的长度,只需要遍历链表,然后记录链表的长度即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int ListLength (ListPtr list ) { if (list == NULL ) { return ERROR; } int length = 0 ; ListPtr p = list ->next; while (p) { length++; p = p->next; } }
3.6 获取线性表中指定位置的元素 获取线性表中指定位置的元素,只需要遍历链表,然后找到指定位置的元素即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Status GetElem (ListPtr list , int pos, data_t *elem) { if (list == NULL ) { return ERROR; } if (pos < 0 || pos > ListLength(list )) { return ERROR; } ListPtr p = list ->next; int i = 0 ; for (i = 0 ; i < pos; i++) { p = p->next; } *elem = p->data; return OK; }
3.7 获取线性表中指定元素的位置 获取线性表中指定元素的位置,只需要遍历链表,然后找到指定元素的位置即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int LocateElem (ListPtr list , data_t elem) { if (list == NULL ) { return ERROR; } ListPtr p = list ->next; int i = 0 ; while (p) { if (p->data == elem) { return i; } i++; p = p->next; } return ERROR; }
3.8 在线性表中指定位置插入元素 对于链表中,插入元素其实很简单,它不需要移动全部元素,只需要在对应的位置移动指针即可,让插入的结点的next
获得前面的那个结点的next
,然后让前面的那个结点的next
指向这个新插入的结点。
图示如下:
有一个链表,第一个是头结点,头节点不算在内,现在需要插入到第1的位置
先找到要插入位置的结点的前面,相当于是0这个位置,然后让新插入的结点的next
指向前面那个结点的next
然后让前面那个结点的next
指向插入的这个结点
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Status ListInsert (ListPtr list , int pos, data_t elem) { linkList pNext = NULL ; linkList pHead = list ; int j = 0 ; if (pos < 1 ) { return ERROR; } if (value == -1 ) { return OK; } while (pHead && pos - 1 > j) { pHead = pHead->pNext; j++; } if (j > pos - 1 ) { return ERROR; } pNext = (linkList)malloc (sizeof (linkNode)); if (pNext == NULL ) { return ERROR; } pNext->pNext = pHead->pNext; pNext->data = elem; pHead->pNext = pNext; return OK; }
3.9 删除元素 删除是比较简单的,其实就是找到想要删除的元素的前一个元素,然后让这个结点的next
指向想要删除的结点的next
,然后释放需要删除的结点就可以删除成功了。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Status ListDelete (ListPtr list , int pos, data_t *elem) { int j = 1 ; data_t value = ERROR; linklist pNext, pEnd; pNext = link->pNext; pEnd = link; if (pos < 1 ) { return ERROR; } if (LinkList_ListEmpty(*link) == IS_NULL) { return ERROR; } while (pNext) { if (j == pos) { pEnd->pNext = pNext->pNext; value = pNext->data; free (pNext); break ; } j++; pEnd = pNext; pNext = pNext->pNext; } return value; }
总结 线性表还是比较简单的一个数据结构,后面还会介绍栈、队列和树这些数据结构。