2020-12-15

本文详细介绍了多种排序算法,包括冒泡排序、插入排序、选择排序、快速排序以及希尔排序的原理和实现。此外,还探讨了链表的基本操作,如逆序打印、逆转节点以及插入和删除节点的方法。这些内容对于理解和优化算法性能至关重要。
摘要由CSDN通过智能技术生成

数据结构与算法笔面试题整理

一、常见时空复杂度有:
*常数级复杂度:O(1)
*对数级复杂度:O(logN)
*线性级复杂度:O(N)
*线性对数级复杂度:O(NlogN)
*平方级复杂度:O(N2)

二、冒泡排序(重点)
(1)算法流程
a.比较两个相邻的元素,如果第一个比第二个大,则交换两个元素的位置;
b.对每一对相邻的元素做同样的工作,从开始的第一对一直到结尾的最后一对,经过这一步,最后的元素将是做大值;
c.针对所有的元素重复以上步骤,除了最后一个;
d.持续对越来越少的元素重复以上步骤,直到没有元素需要交换为止;
(2)算法评价(N代表元素个数)
评价时间复杂度O(N^2),比较稳定的排序方法,对样本的有序性敏感
void bubble(int arr[],int len)
{
int i=0,j=0;
//使用外层循环来控制比较的轮数
for(i = 1;i<len;i++)
{
//使用内层循环控制针对当前轮比较的元素下标
int flag =1;
for(j=0;j<len-i;j++)
{
if(arr[j] > arr[j+1])
{
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
//表明有数据元素发生了交换
flag = 0;
}
}
//表示刚才的一轮扫描中没有发生交换
if(1 == flag)
{
//省略剩余的轮数
break;
}
}
}
三、插入排序算法
(1)算法流程
a.从第一个元素起,该元素可以认为已经有序
b.从下一个元素起依次取出,让取出的元素依次与左边的有序数列进行比较
c.如果左边的元素大于取出的元素,则左边的元素右移
d.如果左边的元素小于等于取出的元素,则将取出的元素插入到左边元素的右边,或者左边不再有元素,则将取出的元素插入到最左边
e.重复以上过程,直到处理完毕所有元素为止
(2)算法评价
平均时间复杂度O(N^2),比较稳定的排序方法,对样本的有序性非常敏感,但是插入排序算法的赋值次数比冒泡少,因此一般情况下略优于冒泡排序
void insert(int arr[],int len)
{
int i=0,j=0;
//从第二个元素起,依次取出每个元素
for(int i=1;i<len;i++)
{
//使用临时变量记录取出的当前元素值
int temp = arr[i];
//使用取出的元素与左边的有序数列依次比较,如果左边的元素大,则左边元素右移;
for(j=i;arr[j-1]>temp && j>=i;j–)
{
arr[j] = arr[j-1];
}
//直到左边元素不再大于取出元素时,插入取出元素
if(j!=i)
{
arr[j] = temp;
}
}
}
四、选择排序
(1)算法流程
a.从第一个元素起依次取出,并且假定取出的元素为最小值,使用min记录该元素的下标
b.使用min记录的元素和后续的元素依次进行比较,如果后续元素中有比min记录的元素还小的元素,则重新记录该元素的下标到min中,也就是后续记录变成了min记录的最小值
c.直到min记录的最小值和后续所有的元素比较完毕,交换min记录的最小值和最开始假定的元素之间的位置,此时最小值被移动到了最左边
d.重复以上过程,直到处理完毕所有元素
(2)算法评价
平均时间复杂度O(N^2),不稳定,对样本的有序性不敏感,虽然该算法比较的次数多,但是交换的次数少,因此一般情况下也是略由于冒泡排序
void choose(int arr[],int len)
{
int i=0,j=0;
//从第一个元素起依次取出,使用min记录下标
for(i=0;i<len-1;i++)
{
int min=i;
//使用取出的元素与后续元素依次比较,如果找到比min记录元素还小的元素,则重新记录下标
for(j=i+1;j<len;j++)
{
if(arr[j]<arr[min])
{
min = j;
}
}
//直到min记录的元素与后续所有元素比较完毕,交换min记录的元素和最开始取出的元素
if(min != i)
{
int temp = arr[i];
arr[i]=arr[min];
arr[min] = temp;
}
}
}
五、快速排序法
(1)算法流程
a.从样本数列中选择中间元素作为基准值,单独保存起来;
b.重组样本数列,将所有小于基准值的元素放在基准值的左边,将所有大于基准值的元素放在基准值的右边,这个过程叫分组
c.以递归的方式分别对小于基准值的分组和大于基准值的分组进行再次分组,直到处理完毕所有的元素为止
(2)算法评价
平均时间复杂度O(NlogN),不稳定,如果每次都能做到均匀分组,则排序速度最快。
void quick(int arr[],int left,int right)
{
//1.寻找中间元素作为基准值,单独保存
int p = (left+right)/2;
int pivot = arr[p];
//2.分别使用左边元素和右边元素与基准值进行比较,将小于基准值的元素放在左边,将大于等于基准值的元素放在右边;
int i=0,j=0;
for(i=left,j=right;i<j;)
{
//如果左边元素存在并且小于基准值时
while(arr[i]<pivot && i<p)
{
i++;
}
//如果左边元素存在,但是大于等基准值
if(i<p)
{
arr[p] = arr[i];
p=i;
}
//接下来处理右边元素
while(pivot<=arr[j]&&p<j)
{
j–;
}
if(p<j)
{
arr[p] = arr[j];
p = j;
}
}
//3.将基准值放在重合的位置上
arr[p]=pivot;
//4.分别对左边分组和右边分组重复以上过程,使用递归处理
if(p-left >1)
{
quick(arr,left,p-1);
}
if(right-p>1)
{
quick(arr,p+1,right);
}
}
六、希尔排序
void sort(int *array,int len){
int temp,i,j,gap;
gap = len;
do
{
gap = gap / 3 + 1;
for(i=0+gap;i<len;i++){
if(array[i]<array[i-gap]){
tmp = array[i];
for(j=i-gap;j>=0;j=j-gap)
if(array[j]>tmp)
array[j+gap]=array[j];
else
break;
array[j+gap]=tmp;
}
}
}while(gap > 1);
}

七、链表
#include<stdio.h>
#include<stdlib.h>

//定义节点的数据类型
typedef struct Node
{
int data;//记录数据元素本身
struct Node* next;//记录下一个节点的地址
}Node;

//定义单链表的数据类型
typedef struct
{
Node* head;//记录头节点的地址
Node* tail;//记录尾节点的地址
int cnt;//记录元素的个数
}List;

//清空链表中所有的节点
void clear(List* pl)
{
while(-1 != pop_head(pl));
}

//逆序打印链表中的所有节点元素
void reverse_travel_data(List* pl)
{
//1.调用递归函数进行打印
reverse_travel(pl->head);
//2.打印换行符增加美观
printf("\n");
}

//实现逆序打印的递归函数
void reverse_travel(Node* pn)
{
if(pn != NULL)
{
//1.打印后续节点元素值,使用递归
reverse_travel(pn->next);
//2.打印头节点元素值
printf("%d",pn->data);
}
}

//实现逆转的递归函数
void reverse(Node* pn)
{
//确保表中至少有两个节点才需要逆转
if(pn != NULL && pn->next != NULL)
{
//采用递归逆转后续的元素
reverse(pn->next);
//逆转两个节点的方法
pn->next->next = pn;
pn->next = NULL;
}
}

//逆转链表中所有节点
void reverse_data(List* pl)
{
//1.调用递归函数进行逆转
reverse(pl->head);
//2.交换head和tail的指向
Node* pt = pl->head;
pl->head = pl->tail;
pl->tail = pt;
}

//删除指定下标位置的节点
int delete_data(List* pl,int pos)
{
//1.判断下标位置是否合法
if(pos < 0 || pos >= size(pl))
{
printf(“下标位置不合法,删除失败\n”);
return -1;
}
//2.当pos=0时,删除头节点
if(0 == pos)
{
return pop_head(pl);
}
//3.当pos=cnt-1时,删除尾节点
if(size(pl)-1 == pos)
{
return pop_tail(pl);
}
//4.当pos为其他值时,删除中间节点
Node* pt = pl->head;
int i=0;
for(i=1;i<pos;i++)
{
pt = pt->next;
}
Node* pm = pt->next;
pt->next = pm->next;
int temp = pm->data;
free(pm);
pm = NULL;
return temp;
}

//删除尾节点的功能函数
int pop_tail(List* pl)
{
//判断链表是否为空
if(empty(pl))
{
return -1;
}
//当链表中只有一个节点时
if(1==size(pl))
{
int temp = pl->head->data;
free(pl->head);
pl->head = pl->tail=NULL;
return temp;
}
//当链表中有更多的节点时,采用归纳法
//先将相对于cnt=2时多出来的next执行完毕
Node* pt = pl->head;
int i=0;
for(i=2;i<size(pl);i++)
{
pt = pt->next;
}
//接下来写cnt=2时的删除代码
int temp = pl->tail->data;
free(pl->tail);
pl->tail = pt;
//为了避免记录已经删除节点的地址
pl->tail->next = NULL;
return temp;
}

//实现获取头节点的元素值
int get_head(List* pl)
{
return empty(pl)?-1:pl->head->data;
}

//实现获取尾节点的元素值
int get_tail(List* pl)
{
return empty(pl)?-1:pl->tail->data;
}

//实现删除头节点的功能
int pop_head(List* pl)
{
if(empty(pl))
{
return -1;
}
Node* pt = pl->head;
int temp = pt->data;
pl->head = pt->next;
free(pt);
pt=NULL;
//当链表中只有一个节点时,tail置为NULL
if(NULL == pl->head)
{
pl->tail = NULL;
}
return temp;
}

//向链表中任意的下标位置插入新元素
void insert_data(List* pl,int pos,int data)
{
//1.判断坐标是否合法
if(pos<0 || pos>size(pl))
{
pos = size(pl);//默认插入到链表的尾部
}
//2.将新元素插入指定的位置
if(0==pos)
{
push_head(pl,data);
return;
}
if(size(pl) == pos)
{
push_tail(pl,data);
return;
}
//接下来就是插入到中间位置的情况
Node* pn = create_node(data);
Node* pt = pl->head;
//使用for循环将相对于pos=1多出来的next走完
int i = 0;
for(i=1;i<pos;i++)
{
pt = pt->next;
}
//接下来把pos=1时的代码写下来即可
pn->next = pt->next;
pt->next = pn;
//让节点个数加一
++pl->cnt;
}

//向链表的末尾追加新元素
void push_tail(List* pl,int data)
{
//1.创建新节点
Node* pn = create_node(data);
//2.将新节点插入到链表的末尾
if(empty(pl))
{
pl->head = pn;
}
else
{
pn->tail->next = pn;
}
pl->tail = pn;
//3.将节点的个数加1
++pl->cnt;
}

//编写创建新节点的函数
Node* create_node(int data)
{
Node* pn = (Node*)malloc(sizeof(Node));
if(NULL == pn)
{
printf(“创建节点失败\n”);
exit(-1);
}
pn->data = data;
pn->next = NULL;
return pn;
}

//判断链表是否为空
int empty(List* pl)
{
return NULL == pl->head;
}

//判断链表是否为满
int full(List* pl)
{
return 0;
}

//计算链表中节点个数
int size(List* pl)
{
return pl->cnt;
}

//向头节点位置插入新元素的功能
void push_head(List* pl,int data)
{
//1.创建新节点,初始化
Node* pn = (Node*)malloc(sizeof(Node));
if(NULL == pn)
{
printf(“创建节点失败\n”);
return;
}
pn->data = data;
pn->next = NULL;
Node* pn = create_node(data);
//2.将新节点插入到头节点的位置
if(empty(pl))
{
pl->head=pl->tail=pn;
}
else
{
pn->next = pn->head;
pl->head = pn;
}
//节点元素的个数加1
++pl->cnt;
}

//遍历链表中的所有节点元素值
void travel(List* pl)
{
printf(“链表中的元素有:”);
Node * pt = pl->head;
while(pt != NULL)
{
printf("%d",pt->data);
pt=pt->next;
}
printf("\n");
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值