使用VUE实现快速排序可视化

使用VUE实现快速排序可视化

在这里插入图片描述

VUE 特点

VUE是以数据为驱动的,且内置了双向绑定 v-model
双向绑定的本质是为对象添加property,并且重写 get,set 方法,当然也可以使用为对象添加代理 proxy进行实现

手撸“双向绑定”

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>双向绑定</title>
</head>
<body>
    <input id="input" type="text" value="">
    <span id="span"></span>
</body>
<script>
    var dom1 = document.getElementById("input");
    var dom2 = document.getElementById("span");
    var data = {
        'message':""
    }
    Object.defineProperty(data,'_message',{
        get() {
            return data.message;
        },
        set(value){
            if (data.message != value)
                data.message =  value;
            console.log(value);
        }
    })

    dom1.addEventListener('input',function (e) {
        data._message = e.target.value;
        span.innerHTML = data.message;
    })

</script>
</html>

在这里插入图片描述

排序可视化分析

排序可视化主要有两种实现方式

  1. queue 队列
  2. Generator 迭代器

队列即提前做好排序工作获取所有的列表状态 state 然后通过定时器将状态逐一抛出,实现可视化

迭代器即利用委托,利用 next 获取返回的动作,并通过定时器实现可视化

本文使用迭代器实现

变量的定义

	list_str :'', // 与用户交互双向绑定
	list : [], // 通过watch监听list_str实时修改list数组 用于排序
	iter:null, // 迭代器
	isStart:false, // 标志是否正在排序
	isPause:false, // 标志是否暂停排序
	sentinel:[{'name':'哨兵1','index':0},{'name':'哨兵2','index':0}], // 存储哨兵位置
	queue : [], // 用于上一步下一步 暂未实现
	info : '已停止', // 存放输出信息

重要函数的定义

	function * f(left,right) //委托管理
	start_sort() // 开始排序
	stop_sort() // 结束排序
	refuse_sort() // 恢复排序
	pause_sort() // 暂停排序

定时器

定时器是最后执行的。

	var sleep = 500;
	var start= ()=>{ // 匿名函数
		/*
			 main code
		*/
		setTimeout(start,sleep );
	};
	setTimeout(start,0);

具体实现

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>QuickSort Viewer</title>
    <link rel="stylesheet" type="text/css" href="main.css" />
    <link rel="stylesheet" type="text/css" href="layui/css/layui.css" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="jquery-1.11.3.min.js"></script>
    <script src="layer/layer.js"></script>
    <script src="layui/layui.js"></script>
    <script src="main.js"></script>
</head>
<body>
<div id="app">
    <div class="header">
        <span style="color: #f0f0f0">QuickSort Viewer</span>
        <div class= "flex-box">
            <div class="layui-inline">
                <div class="layui-input-inline">
                    <input type="text" autocomplete="off" placeholder="" class="layui-input"  v-model="list_str">
                </div>
            </div>
            <div class="layui-inline" style="margin-left: 10px">
                <div class="layui-btn-group">
                    <button type="button" class="layui-btn layui-btn-primary layui-btn-sm" :class="{'layui-btn-disabled':isStart && !isPause}" @click="isStart?refuse_sort():start_sort()"><i class="layui-icon layui-icon-play"></i></button>
                    <button type="button" class="layui-btn layui-btn-primary layui-btn-sm" :class="{'layui-btn-disabled':!(!isPause && isStart)}" @click="pause_sort()"><i class="layui-icon layui-icon-pause
"></i></button>
                    <button type="button" class="layui-btn layui-btn-primary layui-btn-sm" :class="{'layui-btn-disabled':!isStart}" @click="stop_sort()"><i class="layui-icon layui-icon-delete
" ></i></button>
                    <button type="button" class="layui-btn layui-btn-primary layui-btn-sm" :class="{'layui-btn-disabled':isStart}"  @click="get_random()"><i class="layui-icon layui-icon-set
"></i></button>
                </div>
            </div>

        </div>
    </div>

    <div class="container">
        <div class="left" id="view">
            <div  class="canvas" v-if="isStart" :style="{ height:scale + 'px'}">
                <div :class="{box:true}" v-for="(item,index) in sentinel"
                     :style="{ width:scale + 'px',height:scale + 'px',transform: 'translateX(' +  item.index * 100 + '%)' }">
                    <div :class="{item:true,select:item.select,disable:item.disable}">{{item.name}}</div>
                </div>
            </div>

            <div  class="canvas" :style="{ height:scale + 'px'}">
                <div :class="{box:true}" v-for="(item,index) in list"
                     :style="{width:scale + 'px',height:scale + 'px', transform: 'translateX(' +  item['index'] * 100 + '%)' }">
                    <div :class="{item:true,select:item.select,disable:item.disable}" >{{item.value}}</div>
                </div>
            </div>
        </div>
        <div class="right">
            <div class="info">
                <p>当前步骤:</p>
                <p>{{info}}</p>
            </div>

            <div class="author">
                <div  style="width:100px;height: 100px;border-radius:50px;margin-bottom:20px;background-image: url(https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1587314370721&di=f5ff40d6dc2ed7f563dee580495804bd&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fforum%2Fw%3D580%2Fsign%3D06f68d6457da81cb4ee683c56267d0a4%2F9a10ae246b600c332cdcbb581c4c510fd8f9a169.jpg);"></div>
                <p> STDCHI </p>
            </div>

            <button type="button" class="layui-btn layui-btn-fluid">关于可视化的说明</button>

        </div>
    </div>

</div>



</body>
</html>

js



    function _alert(msg,icon) {
        layer.msg(msg, {icon: icon});
    }

    var sleep= 500;
    var ARRAY = [3,100,2,1,4,5,9]; // global var
    var INDEX = [0,1,2,3,4,5,6];

    function swap(id1,id2) {
        var t;
        t = ARRAY[id1];
        ARRAY[id1] = ARRAY[id2];
        ARRAY[id2] = t;
        t = INDEX[id1];
        INDEX[id1] = INDEX[id2];
        INDEX[id2] = t;
    }
    
    function * f(left,right) {
        if (left >= right) {
            if (left == right)yield {'method':'disable','data':INDEX[left]};
            return;
        }
        var i,j,temp;
        i = left;j = right;
        temp  = ARRAY[left];

        yield {'method':'moves','data':[[1,left],[2,right]]};
        yield {'method':'select','data':INDEX[left]};

        while(i != j){
            while(ARRAY[j] >= temp && i < j) {
                j--;
                yield {'method':'move','data':[2,j]};
            }
            while(ARRAY[i] <= temp && i < j) {
                i++;
                yield {'method':'move','data':[1,i]};
            }
            if(i < j)
            {
                swap(i,j);
                yield {'method':'selects','data':[INDEX[i],INDEX[j]]};
                yield {'method':'swap','data':[INDEX[i],INDEX[j]]};
                yield {'method':'deselects','data':[INDEX[i],INDEX[j]]};
            }
        }
        yield {'method':'deselect','data':INDEX[left]};
        yield {'method':'disable','data':INDEX[left]};
        swap(left,i);
        yield {'method':'swap','data':[INDEX[left],INDEX[i]]};
        // 递归委托
        yield * f(left,i - 1);
        yield * f(i + 1,right);
    }



    var App = new Vue({
        el:'#app',
        data(){
            return{
                list_str :'', // 与用户交互的字符串
                list : [], // 排序使用的数组 [{index,value}]
                iter:null, // 迭代器
                isStart:false, // 是否开启
                isPause:false, // 是否暂停
                sentinel:[{'name':'哨兵1','index':0},{'name':'哨兵2','index':0}],
                queue : [],
                info : '已停止',
                scale:0,
            }
        },
        methods:{
            reset_list(){
                this.list_str = '3,100,2,1,4,5,9'
            },
            init_sort(){
                // sort init
                this.iter = f(0,this.list.length - 1);
                ARRAY = [];
                INDEX = [];
                this.sentinel = [{'name':'哨兵1','index':0},{'name':'哨兵2','index':this.list.length - 1}];
                for (let i = 0; i < this.list.length; i++) {
                    this.list[i].index = i;
                    this.list[i].select = false;
                    this.list[i].disable = false;
                    ARRAY.push(this.list[i].value);
                    INDEX.push(i);
                }
            },
            start_sort(){
                if(this.isStart && !this.isPause) return;
                if (!this.isPause) {
                    this.init_sort();
                }
                this.isStart = true;
                this.isPause = false;
                var start = ()=>{
                    if(!this.isStart || this.isPause) return;
                    var i = this.iter.next();
                    if (!i.done){
                        console.log(i.value.method,i.value.data);
                        var a,b,t;
                        if(i.value.method == 'swap'){
                            a = i.value.data[0];
                            b = i.value.data[1];
                            this.set_info("交换(" + this.list[a].index.toString()+ " | " + + this.list[b].index.toString()+ ")" );
                            t = this.list[a].index;
                            this.list[a].index = this.list[b].index;
                            this.list[b].index = t;

                        }else if(i.value.method == 'move'){
                            a = i.value.data[0];
                            b = i.value.data[1];
                            this.sentinel[a - 1].index = b;
                            this.set_info("哨兵[" + a.toString()+ "] move --> " + b.toString());
                        }else if(i.value.method == 'moves'){
                            a = i.value.data;
                            for (let j = 0; j < a.length; j++) {
                                this.sentinel[a[j][0] - 1].index = a[j][1];
                            }

                        }else if(i.value.method == 'select'){
                            a = i.value.data;
                            this.list[a].select = true;
                            this.set_info("选择基准点(" + this.list[a].index.toString()+ ")" );
                        }else if(i.value.method == 'selects'){
                            a = i.value.data;
                            for (let j = 0; j < a.length; j++) {
                                this.list[a[j]].select = true;
                            }
                        }else if(i.value.method == 'deselect'){
                            a = i.value.data;
                            this.list[a].select = false;
                        }else if(i.value.method == 'deselects'){
                            a = i.value.data;
                            for (let j = 0; j < a.length; j++) {
                                this.list[a[j]].select = false;
                            }
                        }else if(i.value.method == 'disable'){
                            a = i.value.data;
                            this.list[a].disable = true;
                        }
                    }else if(i.done){
                        this.stop_sort();
                    }
                    setTimeout(start,sleep);
                };
                setTimeout(start,0);

            },
            stop_sort(){
                if(!this.isStart) return;
                this.isStart = false;
                this.isPause = false;
                this.set_info("已停止");
                _alert("排序已结束",1);
            },
            refuse_sort(){
                if(!this.isStart || !this.isPause) return;
                this.start_sort();
            },
            pause_sort(){
                if(!this.isStart || this.isPause) return;
                this.isPause = true;
                this.set_info("已暂停");
            },
            get_random(){
                var num = Math.round(Math.random() * 2) + 10; // 10 - 12
                var arr = [];
                for (let i = 0; i < num; i++) {
                    arr.push(Math.round(Math.random() * 1000)); // 1000以内
                }
                this.list_str = arr.toString();
            },
            set_info(msg){
                this.info = msg;
            }
        },
        watch:{
            // 变量监听
            list_str: function (val,ordval) {
                this.list = [];
                let list = val.split(',').map(obj => parseInt(obj));// 映射为整数数组
                for (let i = 0; i < list.length; i++) {
                    this.list.push({'index':i,'value':list[i],'select':false,'disable':false});
                }
                var w = document.getElementById("view").offsetWidth;
                this.scale = parseInt(w / this.list.length);
                console.log(this.list);
            }
        }
    })


    setTimeout(()=>{
        App.reset_list();
        App.get_random();
        _alert("Hello!!!",6);
    },0);

CSS


*{
    padding: 0;
    margin: 0;

    transition: -webkit-transform 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
    transition: transform 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
    transition: transform 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275), -webkit-transform 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);

}


#app{
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    position: fixed;
}


.header{
    height: 40px;
    width: 100%;
    box-shadow: 0 0 1px rgba(0,0,0,0.25);
    transition: background-color 0.3s ease-in-out;
    position: relative;
    background-color: #393D49;
    padding: 10px 60px;
    z-index: 20;
    font-size: 20px;
    display: flex;
    align-items: center;        /*垂直居中对齐*/
}

.flex-box{
    display: flex;
    padding-left: 10px;
    align-items: center;        /*垂直居中对齐*/
}



.canvas {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: row;
    box-sizing:border-box;
}



.box{
    padding: 10px;
    float: left;
    position: absolute;
    text-align: center;
    font-size: 20px;
    box-sizing: border-box;
}

.item{
    width: 100%;
    height: 100%;
    border: 1px solid #f3f3f3;
    border-radius: 10px;
    display: flex;
    justify-content: center;    /*水平居中对齐*/
    align-items: center;        /*垂直居中对齐*/
}

.container{
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
}

.left{
    display: flex;
    flex-direction: column;
    width: 65%;
    height: 100%;
}

.right{
    width: 30%;
    height: 100%;
    background-color: #f0f0f0;
    box-shadow: 0 2px 2px rgba(10,16,20,.24),0 0 2px rgba(10,16,20,.12);
    display: flex;
    flex-direction: column;
    padding: 20px;
    box-sizing: border-box;
}

.info{
    position: absolute;
    width: 100%;
    height: 100%;
    font-size: 30px;
}

.author{
    width: 100%;
    height: 100%;
    padding: 20px;
    box-sizing: border-box;
    font-size: 20px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}


.select{
    border: 2px solid coral;
    /*margin-top: 30px;*/
}

.disable{
    background-color: #f0f0f0;
}

.active{
    /*background-color:blue;*/
}

后续的工作

后续我将封装可视化组件,使用VUECli-3脚手架开发,并且将上传到我的Github上

原创文章 38 获赞 35 访问量 1万+

猜你喜欢

转载自blog.csdn.net/shipsail/article/details/106134796
今日推荐