——算法、线性表——
概念明晰:随机存取、顺序存取、随机存储和顺序存储
下面完整的介绍一下这4个概念
1、存取结构
分为随机存取
和非随机存取
(又称顺序存取)
2、存储结构
分为顺序存储
和随机存储
3、顺序存储结构
4、随机存储结构
--随机存储最典型的代表为链式存储:
链式存储结构特点
1、比顺序存储结构的存储密度小 (每个节点都由数据域和指针域组成,所以相同空间内假设全存满的话顺序比链式存储更多)。
2、逻辑上相邻的节点物理上不必相邻。
3、插入、删除灵活 (不必移动节点,只要改变节点中的指针)。
4、查找结点时链式存储要比顺序存储慢。
5、每个结点是由数据域和指针域组成
一、数据结构的概念
1、基本概念:
2、算法
(1)概念
(2)重要特性:
3、算法的评价标准(“好”的算法应该考虑达到以下目标)
4、算法的时空效率
(1)时间复杂度
根据算法写成的程序在执行时耗费时间的长度,记为T(n) = O(n)
(2)空间复杂度
根据算法写成的程序在执行时占用存储单元的长度记为S(n)
(3)语句频度
一个算法中的语句执行次数称为语句频度或时间频度,记为T(n)
(4)一般O(n)的计算方法:
(5)常见的时间复杂度有以下七种:
① O(1)常数型;② O(log2N)对数型;③ O(N)线性型;④ O(Nlog2N)二维型;⑤ O(N^2)平方型;⑥ O(N^3)立方型;⑦ O(2^N)指数型。
例如:
i=1;① while (i<=n) { i=i*2; ② } 解:语句1的频度是1, 设语句2的频度是f(n),则:2^f(n)<=n;f(n)<=log2n 取最大值f(n)= log2n, T(n)=O(log2n )
二、线性表
1、顺序存储
(1)结构体的定义
typedef int Position; typedef struct LNode * PtrToLNode; struct LNode { ElmenetType Data[ MAXSIZE ]; Position Last; }; typedef PtrToLNode List;
(2)顺序表的初始化
List MakeEmpty() { List L; L = (List)malloc(sizeof(struct LNode)); L->Last = -1; //Last 置为-1 表示表中没有数据元素 Return L; }
-
通过L我们可以访问相应线性表的内容。比如:下标为i 的元素:L->Data[i]
-
查询线性表的长度:L->Last+1;
(3)顺序表的查找(时间复杂度为O(n))
#define ERROR -1 /* 将错误信息 ERROR 的值定义为任一负数都可以 */ Position Find( List L, ElementType X ) { Position i = 0; While( i <= L->Last && L->Data[i] != X) i++; if( i > L->Last) return ERROR; /* 如果没有找到,则返回错误信息 */ else return i; /* 找到后返回的是存储位置 */ }
(4)顺序表的插入 (时间复杂度为O(n))
bool Insert( List L, ElementType X, int i) { /* 在 L 的指定位序 i 前插入一个新元素 X; 位序 i 元素数组位置下标为 i-1 */ Postion j; if(L->Last == MAXSIZE-1) {/* 表空间已满,不能插入 */ printf("表满!\n"); return false; } if( i<1 || i > L->Last+2) {/* 检查插入位序的合法性:是否在 1~n+1; n为当前元素个数,即Last+1 */ printf("位序不合法!\n"); return false; } for( j = L->Last; j >= i-1; j--) /*Last 指向序列最后元素an */ L->Data[j+1] = L->Data[j]; /* 将位序为 i 及以后的元素顺序向后移动 */ L->Data[i-1] = X; /* Last 仍指向最后一个元素 */ L->Last++; return true; }
(5)顺序表的删除(时间复杂度为O(n))
bool Delete(List L, int i) { /*从 L 中删除指定位序 i 的元素,该元素数组下标为 i-1*/ Position j; if(i < 1 || i > L->Last + 1)/* 检查空表及删除位序的合法性*/ { printf("位序%d不存在元素",i); return false; } for( j = i; i <= L->Last; j++) L->Data[j-1] = L->Data[j];/*将位序 i+1 及以后的元素顺序向前移动*/ L->Last--;/*Last 仍指向最后元素*/ return true; }
2、链表存储
(1)结构体的定义(时间复杂度为O(n))
typedef struct LNode * PtrToLNode; struct LNode { ElementType Data; PtrToLNode Next; }; typedef PtrToLNode Position; /*这里的位置是结点的地址 */ typedef PreToLNode List;
(2)求表长(时间复杂度为O(n))
int Length(List L) {//默认该链表是有头结点的 Position p; int i=0; /* 初始化计数器 */ //单向链表的遍历(三部曲) p = L->next; /* p指向表的第 1 个结点 */ while(p) { /* 遍历单链表,统计结点数 */ p=p->next; i++; } return i; }
(3)判空
int ListEmpty(LinkList L) { //若 L 为空,则返回1,否侧返回 0 if(L->Next) //非空 return 0; else return 1; }
(4)查找(时间复杂度为O(n))
①按序号查找 FindKth(时间复杂度为O(n))
#define ERROR -1 /* 一般定义为表中元素不可能取到的值 */ ElementType FindKth(List L, int K) { /* 根据指定的位序 K, 返回 L 中相应的元素 */ Position P; int cnt = 1; /* 位序从 1 开始 */ p = L; /* p 指向 L的第 1 个结点 */ while(p && cnt < K) { p = p->next; cnt++ } if((cnt == K) && p) return p->Data; /* 返回第 K 个 */ else return ERROR; /* 否则返回错误信息 */ }
②按值查找,即定位 Find(时间复杂度为O(n))
#define ERROR NULL /*空地址表示错误 */ Position Find( List L, ElementType X) { Position p = L;/* p指向 L 的第 1 个元素 */ while(p && p->Data != x) { p = p->Next; } if(p) return p; else return ERROR; }
(5)链表的插入(时间复杂度为O(n))
int ListInsert_L(LinkList &L, int i,ElementType e) { p = L; j = 0; while(p&& j<i-1) {//寻找第 i-1 个结点 p = p->next; ++=j; } if(!p || j > i-1) return ERROR;// s = (LinkList)malloc(sizeof(LNode));//生成新结点s s->data = e; //将结点s 的数据域的值 更新为 e s->next = p->next; //将结点s 插入 L 中 p->next = s; return OK; }
(6)创建链表(时间复杂度为O(n))
1、带头结点的【头插法】(时间复杂度为O(n))
/* 带头结点的插入创建 */ void createListHead( Linklist L, int n ) { //建立头结点 L = (LNode*)malloc(sizeof(struct LNode)); L->Next = NULL; //建立单链表(头插法) LNode *temp = NULL; //申请空间,写入数据 for(int i = 0; i < n; i++) { tmp = (LNode*)malloc(sizeof(struct LNode)); /* 申请、填装结点 */ scanf("%d",&tmp->Data);//输入元素值 //插入到头结点的后面 tmp->Next = L->Next; L->Next = tmp; } }
2、带尾结点的插入【尾插法】(时间复杂度为O(n))
/*带尾结点的插入*/ void CreateList_L( Listlist &L, int n ) { //正位序数输入 n 个元素的值,建立带表头结点的单链表L //建立头结点 L = (LNode*)malloc(sizeof(struct LNode)); L->Next = NULL; //建立单链表(尾插法) LNode r = L; //尾指针指向头结点 //申请空间,写入数据 for(int i = 0;i < n; i++) { LNode *tmp = (LNode*)malloc(sizeof(struct LNode)); /* 申请新结点 */ scanf("%d",&tmp->Data); //输入元素 tmp->Next = NULL; //插入到尾结点后面 r->next = temp; r = tmp; //r指向新的尾结点 } }
(7)删除(时间复杂度为O(n))
//将线性表L 中第 i 个数据元素删除 int ListDelete_L(LinkList &L, int i, ElementType &e) { p=L; int j=0; while(p->next && j < i-1) {//寻找第 i 个结点,并令p指向其前驱 p = p>next; ++j; } if(!(p->next)||j < i-1) return ERROR;//删除位置不合理 q = p->next; //临时保存被删除结点的地址以备释放 p->next = q->next; //改变被除结点的驱结点的指针域 e = q->data; //保存被删除结点的数据域 free(q); //释放被删除结点的空间 return OK; }
3、二者时间复杂度和优缺点的比较
1、两者复杂度比较
查找 | 插入 | 删除 | |
---|---|---|---|
顺序表 | O(1) | O(1) | O(n)通过下标直接找到待操作元素,主要时间花在移动元素上。 |
链表 | O(n) | O(n)主要时间用于找到插入元素的位置 | O(n)主要时间用于找到待删除元素的位置 |
2、两者优缺点比较
数组 | 优点 | 缺点 |
---|---|---|
随机访问性强;查找速度快 | 插入和删除效率低;可能浪费内存;内存空间要求高,必须有足够的连续内存空间;数组大小固定,不能动态拓展 |
链表 | 优点 | 缺点 |
---|---|---|
插入删除速度快;内存利用率高,不会浪费内存;大小没有固定,拓展很灵活。 | 不能随机查找,必须从第一个开始遍历,查找效率低 |
三、栈
1、栈的顺序存储实现
(1)顺序栈结构体的定义
typedef int Position; typedef int ElementType; typedef struct SNode *PtrToNode; struct SNode { ElementType * Data; /*存储元素的数组*/ Position Top; /*栈顶指针*/ int MaxSize; /*堆栈最大容量*/ }; typedef PtrToNode Stack;
(2)顺序栈的创建
Stack CreateStack(int MaxSize) /*顺序栈的创建*/ { Stack S = (Stack)malloc(sizeof(struct SNode)); S->Data = (ElementType *)malloc(MaxSize * sizeof(ElementType)); S->Top = -1; /*"-1"表示空栈 "MaxSize-1"表示满栈*/ S->MaxSize = MaxSize; /*指定栈的最大容量*/ return S; }
(3)判满
bool IsFull(Stack S) /*判断栈是否满了*/ { return(S->Top == S->MaxSize-1); }
(4)判空
bool IsEmpty(Stack S) /*判断堆栈是否为空*/ { return(S->Top == -1); }
(5)入栈
bool Push(Stack S, ElementType X) /*顺序栈的 入栈 操作*/ { if(IsFull(S)) { printf("堆栈满!"); return false; } else { S->Data[++(S->Top)] = X; /*若是栈不满,则Top加 1,并将新元素放入Data数组的Top位置中*/ return true; } }
(6)出栈
ElementType Pop(Stack S) /*顺序栈 的 出栈 操作*/ { if(IsEmpty(S)) { printf("堆栈空!"); return ERROR; /*ERROR 是 ElementType 类型的特殊值,标志错误。必须是正常栈元素数据不可能取到的值 */ } else return(S->Data[(S->Top)--]); /*若不空,返回Data[Top],同时将Top减 1*/ }
2、栈的顺序存储实现
(1)顺序栈结构体的定义
typedef struct SNode *PtrToSNode; typedef int ElementType; struct SNode { ElementType Data; PtrToSNode Next; }; typedef PtrToSNode Stack;
(2)顺序栈的创建
Stack CreateStack() { /*构建一个堆栈的头结点,返回该结点指针*/ Stack S; S = (Stack)malloc(sizeof(struct SNode)); S->Next = NULL; return S; }
(3)判空
bool IsEmpty(Stack S) { /*判断堆栈 S 是否为空,若是返回 true,否则返回 false*/ return(S->Next == NULL); }
(4)判满 注意:链栈,不必判断堆栈是否满
(5)入栈
bool Push(Stack S, ElementType X) { /*将元素 X 压入堆栈 S */ PtrToSNode TmpCell; TmpCell = (PtrToSNode)malloc(sizeof(struct SNode)); TmpCell->Data = X; //头插法 TmpCell->Next = S->Next; S->Next =TmpCell; return true; }
(6)出栈
ElementType Pop(Stack S) ElementType Pop(Stack S) { /*删除并返回堆栈 S 的栈顶元素*/ PtrToSNode FirstCell; ElementType TopElem; if(IsEmpty(S)) { printf("堆栈空!"); return ERROR; } else { FirstCell = S->Next; TopElem = FirstCell->Data; S->Next = FirstCell->Next; free(FirstCell); return TopElem; } }/*顺序栈 的 出栈 操作*/ { if(IsEmpty(S)) { printf("堆栈空!"); return ERROR; /*ERROR 是 ElementType 类型的特殊值,标志错误。必须是正常栈元素数据不可能取到的值 */ } else return(S->Data[(S->Top)--]); /*若不空,返回Data[Top],同时将Top减 1*/ }
3、栈的应用
四、队列
1、队列的顺序存储实现
(1) 循环队列的结构体定义
typedef int Status; typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */ /* 循环队列的顺序存储结构 */ typedef struct QNode { QElemType data[MAXSIZE]; int front; /* 头指针 */ int rear; /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */ }SqQueue;
(2)生成空队列
/* 初始化一个空队列Q */ Status CreateQueue(SqQueue *Q) { SqQueue *Q = (SqQueue)malloc(sizeof(struct QNode)); Q->data = (ElementType*)malloc(MaxSize * sizeof(ElementType)); Q->front = Q->rear = 0; return OK; }
(3)判空
bool IsEmpty(SqQueue *Q) { return(Q->front == Q->rear); }
(4)判满
bool IsFull(SqQueue *Q) { return((Q->rear+1)% MaxSize == Q->front); }
(5)入队
/* 若队列未满,则插入元素e为Q新的队尾元素 */ Status EnQueue(SqQueue *Q,QElemType e) { if ((Q->rear+1)%MAXSIZE == Q->front) /* 队列满的判断 */ return ERROR; Q->data[Q->rear]=e; /* 将元素e赋值给队尾 */ Q->rear=(Q->rear+1)%MAXSIZE;/* rear指针向后移一位置, */ /* 若到最后则转到数组头部 */ return OK; }
(6)出队
/* 若队列不空,则删除Q中队头元素,用e返回其值 */ Status DeQueue(SqQueue *Q,QElemType *e) { if (Q->front == Q->rear) /* 队列空的判断 */ return ERROR; *e=Q->data[Q->front]; /* 将队头元素赋值给e */ Q->front=(Q->front+1)%MAXSIZE; /* front指针向后移一位置, */ /* 若到最后则转到数组头部 */ return OK; }
2、队列的链式存储实现
(1)队列的链式存储结构体定义
typedef int Status; typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */ typedef struct QNode /* 结点结构 */ { QElemType data; struct QNode *next; }QNode,*QueuePtr; typedef struct /* 队列的链表结构 */ { QueuePtr front,rear; /* 队头、队尾指针 */ }LinkQueue;
(2)生成空队列
/* 构造一个空队列Q */ Status InitQueue(LinkQueue *Q) { Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode)); if(!Q->front) exit(OVERFLOW); Q->front->next=NULL; return OK; }
(3)判空
Status QueueEmpty(LinkQueue Q) { if(Q.front==Q.rear) return TRUE; else return FALSE; }
(4)判满 链式队列,不必判断堆栈是否满
(5)入队
/* 插入元素e为Q的新的队尾元素 */ Status EnQueue(LinkQueue *Q,QElemType e) { QueuePtr s=(QueuePtr)malloc(sizeof(QNode)); if(!s) /* 存储分配失败 */ exit(OVERFLOW); s->data=e; s->next=NULL; Q->rear->next=s; /* 把拥有元素e的新结点s赋值给原队尾结点的后继,见图中① */ Q->rear=s; /* 把当前的s设置为队尾结点,rear指向s,见图中② */ return OK; }
(6)出队
/* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */ Status DeQueue(LinkQueue *Q,QElemType *e) { QueuePtr p; if(Q->front==Q->rear) return ERROR; p=Q->front->next; /* 将欲删除的队头结点暂存给p,见图中① */ *e=p->data; /* 将欲删除的队头结点的值赋值给e */ Q->front->next=p->next;/* 将原队头结点的后继p->next赋值给头结点后继,见图中② */ if(Q->rear==p) /* 若队头就是队尾,则删除后将rear指向头结点,见图中③ */ Q->rear=Q->front; free(p); return OK; }
五、栈和队列操作的特点
相同点 | 不同点 | |
---|---|---|
堆栈(FILO) | 只允许在端点处插入和删除元素; | 栈是先进后出或者后进先出;栈是只能在表的一端进行插入和删除操作的线性表 |
队列(FIFO) | 只允许在端点处插入和删除元素; | 队列是先进先出;队列是只能在表的一端进行插入,然后在另外一端进行删除操作的线性表 |
六、数组存储地址的计算
数组类型 | 存储地址的计算(a是数组首地址,len是每个数组元素所占长度) |
---|---|
一维数组 | a[i]的存储地址:a+i*len |
二维数组:a[m] [n] | 按行存储:a+(i * n+j) * len;按列存储:a+(j * m+i) * len |
例子:数组存储地址的计算示例: 1)已知一维数组a中每个元素占用2个字节,求a[10]的存储地址? 答:a[10]的存储地址为:a+10*2=a+20 2)已知二维数组a[4][5]中, 每个元素占用2个字节,求元素a[3][2]按行为主序存储的存储地址和按列为主序存储的存储地址? 答: 按行存储:a+(35+2) *2 = a+34 按列存储:a+(24+3) *2 = a+22