因为要准备笔试,要涉及到一些数据结构和算法基础。所以准备重新学习和整理下数据结构算法的的知识。按照但不全按照下图的思维导图展开。
线性结构
定义: 元素一条线布局,可以不同,有先后顺序。除了第一个没有直接前驱,最后一个没有直接后继。其他节点都有直接前驱和后继。下存储可分顺序存储和链式存储。
顺序存储
定义: 元素放在一块连续的内存区域中,物理地址和逻辑地址一样。是一种随机存取的存储结构。
操作
(1)定义
typedef struct{
DataType list[ListSize];
int length;
}SeqList;
(2)初始化
//初始化顺序表
void InitList(SeqList *L){
//将其长度初始化为0
L ->length =0;
}
(3)判断是否为空
//判断线性表是否为空,空返回1,否则返回0
int ListEmpty(SeqList L){
if(L.length ==0)
return 1;
else
return 0;
}
(4)按序号查找
//查找顺序表对应位置元素值,若存在赋值给指针变量e
int GetElem(SeqList L,int i,DataType *e){
//判断序号是否越界
if(i<1||i>L.length)
return -1;
*e = L.list[i-1];
return 1;
}
(5)按内容查找
//查找线性表中元素值为e的元素
int LocateElem(SeqList L,DataType e){
int i;
for (i = 0 ; L.length ;i++)
if(L.list[i] == e)
return i+1;
return 0;//找不到返回0
}
(6)插入操作
//在顺序表的第i个位置插入元素e,成功返回1,失败返回-1,顺序表满了返回0
int InsertList(SeqList *L,int i ,DataType e){
int j;
//判断插入位置是否越界,越界返回-1
if(i<1|| i> L->length+1){
return -1;
}
//判断数组容量是否已满,已满返回0
else if(L->length >= ListSize){
return 0;
}else{
//对应位置数据后移
for(j=L->length ; j >=i ;j--){
L->list[j] = L ->list[j-1];
}
L->list[i-1] =e ;//插入元素到i个位置
L->length =L->length+1;
return 1;
}
}
(7)删除操作
//删除顺序表中的第i个位置的元素e,如果容器为空返回0,位置越界返回-1,成功返回1并将删除变量由指针变量e带出
int DeleteList(SeqList *L,int i ,DataType *e){
int j;
//判断是否为空,为空返回0
if(L->length<=0){
return 0;
}
//判断访问是否越界,越界返回-1
else if(i<1||i>L-length){
return -1;
}else{
//记录值
*e = L ->list[i-1];
//数据前移
for(j=i;j<=L->length-1;j++){
L->list[j-1] =L->list[j];
}
//更新长度
L->length = L->length-1;
return 1;
}
}
小结:
优点:存取时间复杂度为O(1),操作简单。易于理解,程序员不用太关心指针内存,安全。
缺点:增加和删除元素复杂度为O(n),需要事先分配好内存空间,难以适应需求。
链式存储
定义: 元素放在一块连续或者非连续的的内存区域中,物理地址和逻辑不一样。链式节点中有一片用来存数据(数据域)另一片用来存下一个节点的地址(指针域)。下可分为单链,双链表,循环单/双链表。
操作:
单链表为例,为了方便操作的统一(如在首元节点前插入或则删除首元节点时),会给链表前面添加一个数据域无效的头节点。为了防止节点指针域指向一些非法内存区域导致程序运行错误甚至是终止,指针域应该赋值为NULL。
(1)定义
typedef struct Node{
DataType data;
struct Node *next;
}ListNode,*LinkList;
(2)初始化单链表
//初始化单链表为其分配内存空间
void InitList(LinkList *head){
//为头结点分配存储空间
if((*head=(LinkList)malloc(sizeof(ListNode)))==NULL){
exit(-1);
}
//将单链表的头结点指针域置为空
(*head)->next = NULL;
」
(3)判断单链表是否为空
//判断单链表是否为空,为空返回1,否则返回0
int ListEmpty(LinkList head){
if(head->next == NULL){ //单链表为空
return 1;
}
else
{
return 0;
}
}
(4)按序号在单链表中查找元素
//按序号查找单链表中第i个结点的元素并返回
ListNode *Get(LinkList head,int i){
ListNode *p;
int j;
if(ListEmpty(head)){ //如果链表为空
return NULL;
}
if(i<1){ //如果位置异常
return NULL;
}
j =0;
p =head;
while(p->next !=NULL && j<i){//保证p的下个结点不为空
p = p->next;
j++;
}
if(j==i)//找到第i个结点
return p;
else
return NULL;
}
(5)按内容查找单链表中指定的元素
//按内容查找单链表中元素值为e的元素
ListNode *LocateElem(LinkList head,DataType e){
ListNode *p;
p = head->next; //指针p指向第一个结点
while(p){
if(p->data != e){
p=p->next;//继续下一个
}else{
break;
}
}
return p;
}
(6)插入操作
//在单链表的指定位置插入指定元素
int InsertList(LinkList head,int i,DataType e){
ListNode *pre,*p;//定义第i个元素的前驱结点指针pre,新生结点指针p
int j;
pre =head; //指针pre指向头结点
j =0;
while(pre->next!=NULL && j<i-1){ //循环直到直到i元素前驱结点
pre = pre->next;
j++;
}
if(j!=i-1)//如果没找到,插入位置出错
return 0;
//新生一个结点
if((p=(ListNode*)malloc(sizeof(ListNode)))==NULL){
exit(-1);
}
p->data =e; //将e赋值给结点的数据域
p->next =pre->next;
pre->next =p;
return 1;
}
(7)删除操作
//删除单链表的指定位置元素并赋值给指针变量e返回
int DeleteList(LinkList head,int i,DataType *e){
ListNode *pre,*p;
int j;
pre = head;
j = 0;
while(pre->next!=NULL && pre->next->next != NULL && j<i-1){
pre = pre->next;
j++;
}
if(j!=i-1){
return 0;
}
//指针p指向单链表中的第i个结点,并将该结点数据域值赋值给e
p = pre->next;
*e =p->data;
//将前驱结点的指针域指向要删除结点的下一个结点
pre->next =p->next;
free(p);//释放p指向的结点
return 1;
}
小结:
优点:链式结构存储空间灵活,空间既可以连续也可以非连续。插入和删除操作的时间复杂度为O(1).
缺点:由于要用到指针存在内存泄漏和访问非法空间的风险。查找操作的时间复杂度为O(n).操作较为复杂。由于有头节点和指针域的存在,空间利用率也不高。
总结
(1)线性表中的元素之间是一对一的关系,除了第一个元素元素没有直接前驱和最后一个元素没有直接直接后继,其他元素都有直接前驱和直接后继。
(2)线性表有顺序存储和链式存储两种方式。采用顺序存储结构的线性表成为顺序表,采用链式存储结构的线性表成为链表。
(3)顺序表中数据元素的逻辑顺序与物理顺序一致,因此可以随机存取。链表是靠指针域表示元素之间的逻辑关系。
(4)链表又分为单链表和双向链表,这两种链表又可构成单循环链表、双向循环链表。单链表只有一个指针域,指针域指向直接后继结点。双向链表的一个指针域指向直接前驱结点,另一个指针域指向直接后继结点。
(5)顺序表的优点是可以随机存取任意一个元素,算法实现较为简单,存储空间利用率高,缺点是需要预先分配存储空间,存储规模不好确定,插入和删除操作需要移动大量元素。链表的优点是不需要事先确定存储空间的大小,插入和删除元素不需要移动大量元素;缺点是只能从第一个结点开始顺序存取元素,存储单元利用率不高,算法实现较复杂,因涉及指针操作,操作不当会产生无法预料的内存错误。