常见的七种排序

目录

一、插入排序

1、直接插入排序

2、希尔排序(缩小增量排序)

二、选择排序 

3、直接选择排序

 4、堆排序

三、交换排序

5、冒泡排序

6、快速排序

四、归并排序 

7、归并排序

五、总结


一、插入排序

1、直接插入排序

思路:

i 用来遍历数组,拿到一个就放进 tmp,

j 从 i 的前一个开始,每次都和 tmp里的值进行比较,若比tmp的值大,j 的值给到 j+1,j--

直到 j 的值比tmp小,或者 j 减到 <0,循环结束,tmp 的值给到 j+1

  • 时间复杂度:最坏情况下,逆序,O(n^2);最好情况下,有序,O(n)
  • 空间复杂度:O(1)
  • 稳定性稳定
  • 特点:当数据量不多,且基本上趋于有序时,使用直接插入排序很快,趋于O(n)
public class InsertSort {
    public void insertSort(int[] array){
        for (int i = 1; i < array.length; i++) {
            int tmp = array[i];
            int j = i-1;
            for (; j >= 0; j--) {
                if(array[j] > tmp){
                    array[j+1] = array[j];
                }else{
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }
}

 

2、希尔排序(缩小增量排序)

  • 时间复杂度:O(n^1.3)
  • 空间复杂度:O(1)
  • 稳定性:不稳定
  • 特点:是对直接插入排序的优化,在最后进行直接插入排序之前,增加了预排序。
/*
  希尔排序(缩小增量排序):gap每次除2
*/
public class ShellSort {
    public void shellSort(int[] array){
        int gap = array.length;
        while(gap > 1){
            gap /= 2;
            shell(array,gap);
        }
    }
    public void shell(int[] array,int gap){
        for (int i = gap; i < array.length; i++) {
            int tmp = array[i];
            int j = i - gap;
            for (; j >= 0; j -= gap) {
                if(array[j] > tmp){
                    array[j+gap] = array[j];
                }else{
                    break;
                }
            }
            array[j+gap] = tmp;
        }
    }
}

二、选择排序 

3、直接选择排序

思路:(走一遍,找到一个最小值)

i 用来遍历数组,拿到一个下标就放进 mIndex

j 从 i 的后一个开始,遍历数组,遇到比 mIndex里的值 小的就更新 mIndex

这一轮遍历完,mIndex里存的就是最小值的下标,把 i 和 mIndex 下标的元素 交换,i++

优化后的思路:(用left 和 right 来遍历数组,走一遍能找到一个最小值和一个最大值)

left 和 right 分别指向 数组的左右两边,minIndex 和 maxIndex 的初始值是 left

j 从 left 的后一个开始遍历,遍历数组 [left+1,right],遇到比 minIndex里的值 小的就更新 minIndex,遇到比 maxIndex里的值 大的就更新 maxIndex

这一轮遍历完,minIndex里存的就是最小值的下标,maxIndex里存的就是最大值的下标,然后把 left 和 minIndex 下标的元素交换,把 right 和 maxIndex 下标的元素交换,left++,right--,但如果 maxIndex 刚好是left,那么最大值就会被换到 minIndex 下标的位置,就得先更新一下 maxIndex,让 maxIndex = minIndex

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

public class SelectSort {
    public void selectSort(int[] array){
        for (int i = 0; i < array.length; i++) {

            int mIndex = i;

            for (int j = i+1; j < array.length; j++) {
                if(array[j] < array[mIndex]){
                    mIndex = j;
                }
            }
            //走到这,mIndex里存的是[i,array.length)中最小值的下标
            int tmp = array[i];
            array[i] = array[mIndex];
            array[mIndex] = tmp;
        }
    }
}

优化后:

 public void select(int[] array){
        int left = 0;
        int right = array.length-1;
        while(left < right){
            int minIndex = left;
            int maxIndex = left;
            for (int j = left+1; j <= right; j++) {
                if(array[j] < array[minIndex]){
                    minIndex = j;
                }
                if(array[j] > array[maxIndex]){
                    maxIndex = j;
                }
            }
            //走到这,minIndex存的是最小值的下标,maxIndex存的是最大值的下标

            swap(array, left, minIndex);

            //如果最大值的下标是left
            if(maxIndex == left){
                maxIndex = minIndex;
            }
            swap(array, right, maxIndex);

            left++;
            right--;
        }
    }
    public void swap(int[] array,int x,int y){
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }

 4、堆排序

  • 时间复杂度:O(n*log n)  
  • 空间复杂度:O(1)
  • 稳定性:不稳定
  • 堆排的时间复杂度:建大根堆的时间复杂度+排序的时间复杂度,建大根堆的时间复杂度:O(n),排序的时间复杂度:O(n*log n) —— 每次shiftDown 0的时间复杂度是 log n,要 n-1 次,所以堆排的时间复杂度:O(n)+O(n*log n) ≈ O(n*log n)
public class HeapSort {
    public void heapSort(int[] array){
        //首先,建一个大根堆
        createBigHeap(array);
        //然后排序
        int end = array.length-1;
        while(end > 0){
            swap(array,0,end);
            shiftDown(array,0,end);
            end--;
        }
    }
    public void createBigHeap(int[] array){
        for (int parent = (array.length-1-1)/2; parent >= 0; parent--) {
            //每个子树都需要向下调整成大根堆
            shiftDown(array,parent,array.length);
        }
    }
    public void shiftDown(int[] array,int parent,int end){
        int child = 2*parent+1;
        while(child < end){
            if(child+1 < end && array[child] < array[child+1]){
                child++;
            }
            if(array[child] > array[parent]){
                swap(array,child,parent);
                parent = child;
                child = 2*parent+1;
            }else{
                break;
            }
        }
    }
    public void swap(int[] array,int x,int y){
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }
}

三、交换排序

5、冒泡排序

思路:

相邻的两个元素进行比较,i 是趟数,j 是每一趟要比较的次数,每一趟都会把一个最大值放到后面。

  • 时间复杂度:(不考虑优化)O(n^2),如果考虑优化的话,最好情况下可以达到O(n)
  • 空间复杂度:O(1)
  • 稳定性 稳定
public class BubbleSort {
    public void bubbleSort(int[] array){
        //趟数
        for (int i = 0; i < array.length-1; i++) {
            boolean flag = true;
            //1趟
            for (int j = 0; j < array.length-1-i; j++) {
                if(array[j] > array[j+1]){
                    swap(array,j,j+1);
                    flag = false;
                }
            }
            //如果flag还是true,说明这一趟中没有进入过if语句进行交换,说明是元素是有序的
            if(flag){
                break;
            }
        }
    }
    public void swap(int[] array,int x,int y){
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }
}

6、快速排序

  • 时间复杂度:O(n*logn)
  • 空间复杂度:O(logn)
  • 稳定性:不稳定
  • 时间复杂度:每层遍历的都是n,要遍历树的高度层,树的高度是logn,所以时间复杂度是nlogn;空间复杂度:需要额外开辟的空间就是存pivot这个基准需要的空间,由于当左边递归完去递归右边时,左边给基准开辟的空间就会被回收,所以需要额外给pivot开辟的空间就是树的高度,所以空间复杂度是logn
  • 上述快排的时间复杂度和空间复杂度不是最坏的,当数据是顺序或逆序时,二叉树只有左树或只有右树,达到最坏,此时时间复杂度是O(n^2),空间复杂度是O(n)

但我们可以优化代码,不让它出现只有左树或只有右树的情况。

1、优化方法一:(解决划分不均匀的问题)

定义一个mid = (start+end)/2

在找基准之前,判断 start,end,mid,三个下标对应的值,谁是中间的那个,返回下标。

然后,与start下标进行交换。尽量解决划分不均匀的问题 

2、优化方法二:(减少后几层的递归,解决效率问题)

递归到小的子区间时,可以考虑使用插入排序。

我们发现,后几层占了整棵树的大部分结点,递归的次数最多发生在后面。所以,我们可以减少后几层的递归来解决效率问题。递归区间很小的时候,我们就不递归了,使用直接插入排序。(这时数据页越来越有序了,使用直接插入排序的时间复杂度趋近O(n),是很快的)

(1)Hoare 法:

找基准:

把left的下标记录下来为i,并把left下标对应的值放进tmp,

从右边找到一个比tmp小的,从左边找到一个比tmp大的,然后交换。这个过程是个循环,循环的条件是 left < right,一旦left和right相等了,就会出循环,此时left和right下标就是基准,交换i和基准对应的值。到这里,基准的左边都是比它小的(或等于它的),基准的右边都是比它大的(或等于它的)

public class QuickSort {
    public void quick(int[] array,int start,int end){
        if(start >= end){
            return;
        }
        // end-start+1 是 [start,end]这个区间元素的个数
        if(end-start+1 <= 15){
            //对 start 和 end 区间范围内使用插入排序
            insertSort(array,start,end);
            return;
        }
        //找三个值中中间值的下标
        int mid = findMidOfIndex(array,start,end);
        swap(array,mid,start);
        //找基准
        int pivot = partition(array,start,end);
        //pivot 就是基准,然后分而治之
        quick(array,start,pivot-1);
        quick(array,pivot+1,end);

    }

    public void quickSort(int[] array){
        quick(array,0,array.length-1);
    }

    public void insertSort(int[] array,int start,int end){
        for (int i = start+1; i <= end; i++) {
            int tmp = array[i];
            int j = i-1;
            for (; j >= start; j--) {
                if(array[j] > tmp){
                    array[j+1] = array[j];
                }else{
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }
    private int findMidOfIndex(int[] array, int start, int end) {
        int mid = (start+end)/2;
        if(array[start] < array[end]){
            if(array[mid] < array[start]){
                return start;
            }else if(array[mid] > array[end]){
                return end;
            }else{
                return mid;
            }
        }else{
            if(array[mid] > array[start]){
                return start;
            }else if(array[mid] < array[end]){
                return end;
            }else{
                return mid;
            }
        }
    }

    public int partition(int[] array,int left,int right){
        //把left下标记录下来,并把值放进tmp,后面都和tmp进行比较
        int i = left;
        int tmp = array[left];
        // left < right 不能是 <= ,当 left == right 时,说明这一趟走完了,基准的下标找到了
        while(left < right){
            /*
            * 要先从右边找到一个比tmp小的,再从左边找到一个比tmp大的,不能反过来
            * 因为如果反过来了,就可能会出现我从左边找到了一个比tmp大的后,开始从右边找比tmp小的,
            * 但是还没有找到left和right就相等了。此时,left和right下标对应的值就是比tmp大的值
            * 出循环后, swap(array,i,left) 就会将大的值换到基准前面去。所以不能反过来。
            * 按照先从右边找一个比tmp小的的方式,我们会先找到一个比tmp小的,即使还没找到比tmp大的就相遇了,
            * left和right下标对应的值也是比tmp小的值,交换后会将小的值放到前面。
            * 所以,一定要先从右边找比tmp小的值!!!
            */
            //从右面找到一个比tmp小的
            while(left < right && array[right] >= tmp){
                right--;
            }
            //从左面找到一个比tmp大的
            while(left < right && array[left] <= tmp){
                left++;
            }
            //从到这,left下标里存的是比tmp大的值,right下标里存的是比tmp小的值
            swap(array,left,right);
        }
        swap(array,i,left);
        return left;
    }
    public void swap(int[] array,int x,int y){
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }

}

(2)挖坑法: (做题优先使用挖坑法)

找基准:

把left下标对应的值放进tmp,

从右边找到一个比tmp小的(下标是right),放进left下标的坑;再从左边找到一个比tmp大的(下标是left),放进right下标的坑。这个过程是个循环,循环的条件是 left<right,直到left和right相等,退出循环,此时left和right就是基准。将tmp放进基准的这个坑里。到这里,基准的左边都是比它小的(或等于它的),基准的右边都是比它大的(或等于它的)

public class QuickSort2 {
    public void quickSort(int[] array){
        quick(array,0,array.length-1);
    }

    private void quick(int[] array, int start, int end) {
        //先找基准,然后找基准左边的基准,然后找基准右边的基准
        if(start >= end){
            return;
        }
        // end-start+1 是 [start,end]这个区间元素的个数
        if(end-start+1 <= 15){
            //对 start 和 end 区间范围内使用插入排序
            insertSort(array,start,end);
            return;
        }
        //找三个值中中间值的下标
        int mid = findMidOfIndex(array,start,end);
        swap(array,mid,start);
        //找基准
        int pivot = partition(array,start,end);
        quick(array,start,pivot-1);
        quick(array,pivot+1,end);
    }

    public void insertSort(int[] array,int start,int end){
        for (int i = start+1; i <= end; i++) {
            int tmp = array[i];
            int j = i-1;
            for (; j >= start; j--) {
                if(array[j] > tmp){
                    array[j+1] = array[j];
                }else{
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }

    private int findMidOfIndex(int[] array, int start, int end) {
        int mid = (start+end)/2;
        if(array[start] < array[end]){
            if(array[mid] < array[start]){
                return start;
            }else if(array[mid] > array[end]){
                return end;
            }else{
                return mid;
            }
        }else{
            if(array[mid] > array[start]){
                return start;
            }else if(array[mid] < array[end]){
                return end;
            }else{
                return mid;
            }
        }
    }

    private int partition(int[] array, int left, int right) {
        int tmp = array[left];
        while(left < right){
            while(left < right && array[right] >= tmp){
                right--;
            }
            array[left] = array[right];
            while(left < right && array[left] <= tmp){
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;
        return left;
    }

    private void swap(int[] array, int x, int y) {
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }
}

四、归并排序 

7、归并排序

思路:

先分解,再合并

分解到一个一个的元素(递),然后合并(归)

主要逻辑就是,将两个有序的数组合并成一个有序的数组。 

  • 时间复杂度:O(n*logn)
  • 空间复杂度:O(n)
  • 稳定性:稳定
public class MergeSort {
    public void mergeSort(int[] array){
        int start = 0;
        int end = array.length-1;
        int mid = (start+end)/2;
        mergeSortChild(array,start,mid,end);
    }
    public void mergeSortChild(int[] array,int start,int mid,int end){
        if(start == end){
            return;
        }
        int s1 = 0;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = end;
        //分解:分解到start==end,即只有一个元素
        mergeSortChild(array,s1,(s1+e1)/2,e1);
        mergeSortChild(array,s2,(s2+e2)/2,e2);
        //合并
        merge(array,s1,e1,s2,e2);
    }
    //把两个有序数组合成一个有序的数组
    public void merge(int[] array,int s1,int e1,int s2,int e2){
        int s = s1;
        int[] tmpArr = new int[e2-s1+1];
        int k = 0;
        while(s1<=e1 && s2<=e2){
            if(array[s1] < array[s2]){
               tmpArr[k++] = array[s1++];
            }else{
               tmpArr[k++] = array[s2++];
            }
        }
        while(s1 <= e1){
            tmpArr[k++] = array[s1++];
        }
        while(s2 <= e2){
            tmpArr[k++] = array[s2++];
        }
        for (int i = 0; i < k; i++) {
            array[s+i] = tmpArr[i];
        }
    }
}

五、总结

排序方法时间复杂度空间复杂度稳定性
直接插入排序

O(n^2)

最好情况下:O(n)

O(1)稳定
希尔排序O(n^1.3)O(1)不稳定
直接选择排序O(n^2)O(1)不稳定
堆排序O(n*logn)O(1)不稳定
冒泡排序

O(n^2)

最好情况下:O(n)

O(1)稳定
快速排序

O(n*logn)

最坏情况下:O(n^2)

O(logn)

最坏情况下:O(n)

不稳定
归并排序O(n*logn)O(n)稳定

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/561156.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Xamarin.Android中“ADB0020: Android ABI 不匹配。你正将应用支持的“armeabi-v7a;arm64-v8a”异常处理

这里写自定义目录标题 1、问题2、解决 1、问题 在Xamarin.Android中出现ADB0020: Android ABI 不匹配。你正将应用支持的“armeabi-v7a;arm64-v8a”ABI 部署到 ABI“x86_64;x86”的不兼容设备。应创建匹配其中一个应用 ABI 的仿真程序&#xff0c;或将“x86_64”添加到应用生成…

Parade Series - CoreAudio Loopback

Scenario 鉴于业务场景需要&#xff0c; 经过技术路径探索&#xff0c; 发现 comtypes 兼容性过于混乱&#xff0c;故而考虑整合一个 CoreAudio 的轮子dll来解决实际问题&#xff01;std::StringStream ⇒ std::ios::binary ⇒ std::ofstream Loopback.dll #ifndef _DLL_C…

【nvm最新解决方案】Node.js v16.20.2 is not yet released or available

【nvm最新解决方案】Node.js v16.20.2 is not yet released or available 解决办法&#xff1a;下载想安装的node压缩包&#xff0c;放入nvm对应目录。 2024年最新node压缩包地址&#xff1a;https://nodejs.org/dist/ 1、选择对应的node版本&#xff1a;例如&#xff0c;我选的…

如何创建响应式HTML电子邮件模板

在这个适合初学者的指南中&#xff0c;你将学习如何创建一个响应式电子邮件模板。你将跟随逐步说明以及代码片段设计一个在任何设备上都看起来很棒的电子邮件模板。 这个项目非常适合渴望掌握电子邮件设计基础的新手&#xff01; &#xff08;本文视频讲解&#xff1a;java56…

怎么用手机远程控制电脑 远程控制怎么用

怎么用手机远程控制电脑&#xff1a;远程控制怎么用 在这个科技日新月异的时代&#xff0c;远程控制电脑已经成为了很多人的需求。有时&#xff0c;我们可能在外出时突然需要访问家中的电脑&#xff0c;或者在工作中需要远程操控办公室的电脑。这时&#xff0c;如果能用手机远…

Spring 声明式事务控制

1. 编程式事务控制相关对象 1.1 PlatformTransactionManager PlatformTransactionManager 接口是 spring 的事务管理器&#xff0c;它提供了我们常用的操作事务的方法。 PlatformTransactionManager 是接口类型&#xff0c;不同的 Dao 层技术则有不同的实现类。例如:Dao层技…

【Spring】Spring源码中占位符解析器PropertyPlaceholderHelper的使用

开发中经常需要使用到占位符&#xff0c;情况较为复杂时总是手工替换处理显得比较的繁琐&#xff0c;加之往往手工所写效率比不上框架自带的现有方法来的更好更快。Spring在处理yml配置文件时&#xff0c;对于yml文件名的占位符替换处理便是使用了占位符解析器PropertyPlacehol…

深入了解PBKDF2:密码学中的关键推导函数

title: 深入了解PBKDF2&#xff1a;密码学中的关键推导函数 date: 2024/4/20 20:37:35 updated: 2024/4/20 20:37:35 tags: 密码学对称加密哈希函数KDFPBKDF2安全密钥派生 第一章&#xff1a;密码学基础 对称加密和哈希函数 对称加密&#xff1a;对称加密是一种加密技术&…

Windows COM技术:COM介绍、代码演示。

目录 步骤一&#xff1a;理解COM技术 介绍COM的基础知识 1. COM的目的和特点 2. COM的关键概念 3. COM的实现 4. COM与DCOM、ActiveX 讨论COM的用途 1. 软件自动化 2. 插件和扩展 3. 跨语言开发 4. 分布式计算 5. 系统级组件 6. 网络浏览器插件 步骤二&#xff1a…

开源贡献代码之​探索一下CPython

探索一下Cython 本篇文章将会围绕最近给Apache提的一个feature为背景&#xff0c;展开讲讲CPython遇到的问题&#xff0c;以及尝试自己从0写一个库出来&#xff0c;代码也已经放星球了&#xff0c;感兴趣的同学可以去下载学习。 0.背景 最近在给apache arrow提的一个feature因为…

【做一名健康的CSDNer】程序员如何早日脱单?

程序员脱单的策略可以从以下几个方面着手&#xff1a; 拓展社交圈&#xff1a;参加技术交流会、行业聚会、开源社区活动等&#xff0c;不仅可以提升技术能力&#xff0c;还可以结识更多志同道合的人&#xff0c;其中可能就包括潜在的伴侣65。 改善形象和性格&#xff1a;注意个…

【GIS教程】ArcGIS做日照分析(附练习数据下载)

我国对住宅日照标准的规定是:冬至日住宅底层日照不少于1小时或大寒日住宅层日照不少于2小时(通常以当地冬至日正午12时的太阳高度角作为依据)。因冬至日太阳高度角最低&#xff0c;照射范围最小&#xff0c;如果冬至日12&#xff1a;00建筑物底层能够接收到阳光&#xff0c;那么…

探索边缘计算:技术的新疆界

探索边缘计算&#xff1a;技术的新疆界 在当今迅速发展的数字化时代&#xff0c;云计算作为数据处理的主力军已广泛应用。但是&#xff0c;随着物联网&#xff08;IoT&#xff09;设备的急剧增加和数据生成速率的加快&#xff0c;云计算面临着种种挑战。边缘计算因此诞生&…

python爬虫-----深入了解 requests 库下篇(第二十五天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

[阅读笔记15][Orca]Progressive Learning from Complex Explanation Traces of GPT-4

接下来是微软的Orca这篇论文&#xff0c;23年6月挂到了arxiv上。 目前利用大模型输出来训练小模型的研究都是在模仿&#xff0c;它们倾向于学习大模型的风格而不是它们的推理过程&#xff0c;这导致这些小模型的质量不高。Orca是一个有13B参数的小模型&#xff0c;它可以学习到…

从零自制docker-11-【pivotRoot切换实现文件系统隔离】

文章目录 busyboxdocker run -d busybox topcontainerId(docker ps --filter "ancestorbusybox:latest"|grep -v IMAGE|awk {print $1})docker export -o busybox.tar $containerId or sudo docker export 09bbf421d93f > ./busybox.tar tar -xvf busybox.tar -C …

修复vite中使用react提示Fast refresh only works when a file only exports components.

前言 我通过 vite 构建了一个 react 应用并使用 react.lazy 来懒加载组件&#xff0c;但是在使用过程中 一直提示 Fast refresh only works when a file only exports components. Move your component(s) to a separate file.eslint(react-refresh/only-export-components)。…

编译OpenWRT固件

前言 编译环境&#xff0c;我是使用Ubuntu16.04.07 LTS 64位版 1.安装Ubuntu16.04.07 LTS 64 作者写这篇文章的时候lede源码使用debian11编译&#xff0c;对于的就是Ubuntu 20&#xff0c;至于为什么要安装ub16是因为最开始我不清楚要使用ub20安装&#xff0c;用ub16安装的时…

CCF-CSP真题《202312-2 因子化简》思路+python,c++满分题解

想查看其他题的真题及题解的同学可以前往查看&#xff1a;CCF-CSP真题附题解大全 试题编号&#xff1a;202312-2试题名称&#xff1a;因子化简时间限制&#xff1a;2.0s内存限制&#xff1a;512.0MB问题描述&#xff1a; 题目背景 质数&#xff08;又称“素数”&#xff09;是指…

RAG部署 | 使用TensorRT-LLM在Windows上部署检索增强生成聊天机器人RAG

项目应用场景 面向 Windows 平台部署 RAG 检索增强生成聊天机器人场景&#xff0c;项目采用 TensorRT-LLM 进行 GPU 加速推理&#xff0c;注意项目需要 RT4090 及以上的英伟达显卡支持。 项目效果 项目细节 > 具体参见项目 README.md (1) 下载构建好的 Llama2 TensorRT 模型…